Merge branch 'Jalview-JS/develop.JAL-3446.ctrlDown' into
authorBobHanson <hansonr@stolaf.edu>
Thu, 11 Jun 2020 12:13:36 +0000 (07:13 -0500)
committerBobHanson <hansonr@stolaf.edu>
Thu, 11 Jun 2020 12:13:36 +0000 (07:13 -0500)
Jalview-JS/develop

Conflicts:
src/jalview/bin/JalviewJS2.java
src/jalview/gui/Desktop.java
src/jalview/util/Platform.java

215 files changed:
build.gradle
doc/JalviewJS-API.md [new file with mode: 0644]
doc/JalviewJS-startupParams.md [new file with mode: 0644]
doc/JalviewJS-startupParams.xlsx [new file with mode: 0644]
doc/building.html
doc/building.md
doc/releaseprocess.html [new file with mode: 0644]
gradle.properties
j11lib/Jmol-14.29.17-no_netscape.jar [deleted file]
j11lib/Jmol-15.1.3.jar [new file with mode: 0644]
j8lib/Jmol-14.29.17.jar [deleted file]
j8lib/Jmol-15.1.3.jar [new file with mode: 0644]
src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java
src/jalview/analysis/AlignmentSorter.java
src/jalview/analysis/CrossRef.java
src/jalview/analysis/scoremodels/ScoreModels.java
src/jalview/api/JalviewApp.java [deleted file]
src/jalview/api/JalviewJSApi.java [new file with mode: 0644]
src/jalview/appletgui/APopupMenu.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/RedundancyPanel.java
src/jalview/appletgui/SeqPanel.java
src/jalview/appletgui/TreeCanvas.java
src/jalview/appletgui/js/JSFunctionExec.java [moved from src/jalview/javascript/JSFunctionExec.java with 99% similarity]
src/jalview/appletgui/js/JalviewLiteJsApi.java [moved from src/jalview/javascript/JalviewLiteJsApi.java with 99% similarity]
src/jalview/appletgui/js/JsCallBack.java [moved from src/jalview/javascript/JsCallBack.java with 97% similarity]
src/jalview/appletgui/js/JsSelectionSender.java [moved from src/jalview/javascript/JsSelectionSender.java with 99% similarity]
src/jalview/appletgui/js/MouseOverListener.java [moved from src/jalview/javascript/MouseOverListener.java with 99% similarity]
src/jalview/appletgui/js/MouseOverStructureListener.java [moved from src/jalview/javascript/MouseOverStructureListener.java with 99% similarity]
src/jalview/bin/AppletParams.java
src/jalview/bin/ApplicationSingletonProvider.java [new file with mode: 0644]
src/jalview/bin/ArgsParser.java
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/bin/JalviewAppLoader.java [deleted file]
src/jalview/bin/JalviewJS2.java
src/jalview/bin/JalviewJSApi.java [deleted file]
src/jalview/bin/JalviewJSApp.java [new file with mode: 0644]
src/jalview/bin/JalviewLite.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/PDBEntry.java
src/jalview/datamodel/features/FeatureAttributes.java
src/jalview/datamodel/features/FeatureSources.java
src/jalview/datamodel/features/SequenceFeatures.java
src/jalview/ext/ensembl/EnsemblCds.java
src/jalview/ext/ensembl/EnsemblGene.java
src/jalview/ext/ensembl/EnsemblSeqProxy.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/so/SequenceOntology.java
src/jalview/fts/service/pdb/PDBFTSRestClient.java
src/jalview/fts/service/uniprot/UniProtFTSRestClient.java
src/jalview/gui/AlignExportOptions.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationChooser.java
src/jalview/gui/AnnotationColourChooser.java
src/jalview/gui/AnnotationExporter.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/AppJmol.java
src/jalview/gui/AssociatePdbFileWithSeq.java
src/jalview/gui/CalculationChooser.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/ColourMenuHelper.java
src/jalview/gui/CrossRefAction.java
src/jalview/gui/CutAndPasteTransfer.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureEditor.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/Finder.java
src/jalview/gui/FontChooser.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/IdPanel.java
src/jalview/gui/JalviewDialog.java
src/jalview/gui/JvSwingUtils.java
src/jalview/gui/LineartOptions.java
src/jalview/gui/OOMWarning.java
src/jalview/gui/PaintRefresher.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/gui/PromptUserConfig.java
src/jalview/gui/RedundancyPanel.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SequenceFetcher.java
src/jalview/gui/SliderPanel.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/UserQuestionnaireCheck.java
src/jalview/gui/VamsasApplication.java
src/jalview/gui/WebserviceInfo.java
src/jalview/gui/WsJobParameters.java
src/jalview/gui/WsParamSetManager.java
src/jalview/gui/WsPreferences.java
src/jalview/httpserver/HttpServer.java
src/jalview/io/AppletFormatAdapter.java
src/jalview/io/BSMLFile.java [new file with mode: 0644]
src/jalview/io/BackupFiles.java
src/jalview/io/BioJsHTMLOutput.java
src/jalview/io/FileFormats.java
src/jalview/io/FileLoader.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/io/VamsasAppDatastore.java
src/jalview/io/WSWUBlastClient.java
src/jalview/io/cache/AppCache.java
src/jalview/io/gff/Gff3Helper.java
src/jalview/io/gff/InterProScanHelper.java
src/jalview/io/gff/SequenceOntologyFactory.java
src/jalview/io/vamsas/Sequencemapping.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GDesktop.java
src/jalview/jbgui/GPCAPanel.java
src/jalview/jbgui/GPreferences.java
src/jalview/jbgui/GSequenceLink.java
src/jalview/jbgui/GStructureViewer.java
src/jalview/jbgui/GTreePanel.java
src/jalview/project/Jalview2XML.java
src/jalview/rest/RestHandler.java
src/jalview/schemes/ColourSchemes.java
src/jalview/structure/StructureImportSettings.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/urls/IdOrgSettings.java
src/jalview/urls/IdentifiersUrlProvider.java
src/jalview/util/MessageManager.java
src/jalview/util/Platform.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/ViewportRanges.java
src/jalview/workers/AlignCalcManager.java
src/jalview/ws/SequenceFetcher.java
src/jalview/ws/SequenceFetcherFactory.java
src/jalview/ws/jws1/Discoverer.java
src/jalview/ws/jws1/JPredClient.java
src/jalview/ws/jws1/MsaWSClient.java
src/jalview/ws/jws1/SeqSearchWSClient.java
src/jalview/ws/jws1/WS1Client.java
src/jalview/ws/jws2/Jws2Client.java
src/jalview/ws/jws2/Jws2Discoverer.java
src/jalview/ws/jws2/MsaWSClient.java
src/jalview/ws/jws2/SequenceAnnotationWSClient.java
src/jalview/ws/jws2/jabaws2/Jws2Instance.java
src/jalview/ws/jws2/jabaws2/Jws2InstanceFactory.java
src/jalview/ws/rest/RestClient.java
src/jalview/ws/sifts/SiftsClient.java
src/jalview/ws/sifts/SiftsSettings.java
src/jalview/ws/utils/UrlDownloadClient.java
src/javajs/async/Assets.java
src/javajs/async/AsyncSwingWorker.java
src/swingjs/api/JSUtilI.java
swingjs/SwingJS-site.zip
swingjs/differences.txt [new file with mode: 0644]
swingjs/timestamp
swingjs/ver/3.2.9-j11/SwingJS-site.zip
swingjs/ver/3.2.9-j11/differences.txt [new file with mode: 0644]
swingjs/ver/3.2.9-j11/timestamp
swingjs/ver/3.2.9/SwingJS-site.zip
swingjs/ver/3.2.9/differences.txt [new file with mode: 0644]
swingjs/ver/3.2.9/timestamp
template.html [new file with mode: 0644]
test/jalview/analysis/AlignmentSorterTest.java
test/jalview/analysis/CrossRefTest.java
test/jalview/datamodel/features/FeatureAttributesTest.java
test/jalview/ext/ensembl/EnsemblCdnaTest.java
test/jalview/ext/ensembl/EnsemblCdsTest.java
test/jalview/ext/ensembl/EnsemblGeneTest.java
test/jalview/ext/ensembl/EnsemblGenomeTest.java
test/jalview/ext/ensembl/EnsemblSeqProxyTest.java
test/jalview/ext/jmol/JmolCommandsTest.java
test/jalview/ext/jmol/JmolViewerTest.java
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java
test/jalview/gui/AlignFrameTest.java
test/jalview/gui/AlignViewportTest.java
test/jalview/gui/AlignmentPanelTest.java
test/jalview/gui/FreeUpMemoryTest.java
test/jalview/gui/JvSwingUtilsTest.java
test/jalview/gui/PairwiseAlignmentPanelTest.java
test/jalview/gui/PopupMenuTest.java
test/jalview/gui/SeqCanvasTest.java
test/jalview/gui/SeqPanelTest.java
test/jalview/io/AnnotatedPDBFileInputTest.java
test/jalview/io/CrossRef2xmlTests.java
test/jalview/io/FeaturesFileTest.java
test/jalview/io/Jalview2xmlBase.java
test/jalview/io/JalviewExportPropertiesTests.java
test/jalview/io/SequenceAnnotationReportTest.java
test/jalview/project/Jalview2xmlTests.java
test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java
test/jalview/renderer/seqfeatures/FeatureRendererTest.java
test/jalview/schemes/ColourSchemesTest.java
test/jalview/structure/Mapping.java
test/jalview/structure/StructureSelectionManagerTest.java
test/jalview/structures/models/AAStructureBindingModelTest.java
test/jalview/ws/PDBSequenceFetcherTest.java
test/jalview/ws/jabaws/JalviewJabawsTestUtils.java
test/jalview/ws/sifts/SiftsClientTest.java
test/jalview/ws/utils/UrlDownloadClientTest.java
unused/JalviewAppLoader.java [new file with mode: 0644]
unused/JalviewJSApp.java [new file with mode: 0644]
utils/doc/github.css [moved from doc/github.css with 100% similarity]
utils/jalviewjs/_j2sclasslist.txt
utils/jalviewjs/classlists/_jvjmol.txt [new file with mode: 0644]
utils/jalviewjs/site-resources/___j2sflags.htm [new file with mode: 0644]
utils/jalviewjs/site-resources/_jalview_bin_JalviewJS_core.html [moved from utils/jalviewjs/site-resources/jalview_bin_JalviewJS_core.html with 100% similarity]
utils/jalviewjs/site-resources/_jalview_embedded_example1.html [moved from utils/jalviewjs/site-resources/jalview_embedded_example1.html with 79% similarity]
utils/jalviewjs/site-resources/_jalview_embedded_example2.html [new file with mode: 0644]
utils/jalviewjs/template.html

index e4ffa8a..0be087f 100644 (file)
@@ -12,13 +12,25 @@ import java.security.MessageDigest
 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
+import com.vladsch.flexmark.util.data.MutableDataSet
+import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
+import com.vladsch.flexmark.ext.tables.TablesExtension
+import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension
+import com.vladsch.flexmark.ext.autolink.AutolinkExtension
+import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension
+import com.vladsch.flexmark.ext.toc.TocExtension
 
 buildscript {
   repositories {
     mavenCentral()
     mavenLocal()
   }
+  dependencies {
+    classpath "com.vladsch.flexmark:flexmark-all:0.62.0"
+  }
 }
 
 
@@ -992,22 +1004,25 @@ def getDate(format) {
 
 
 task setGitVals {
-  def hashStdOut = new ByteArrayOutputStream()
-  exec {
-    commandLine "git", "rev-parse", "--short", "HEAD"
-    standardOutput = hashStdOut
-    ignoreExitValue true
-  }
 
-  def branchStdOut = new ByteArrayOutputStream()
-  exec {
-    commandLine "git", "rev-parse", "--abbrev-ref", "HEAD"
-    standardOutput = branchStdOut
-    ignoreExitValue true
-  }
+  doFirst {
+    def hashStdOut = new ByteArrayOutputStream()
+    def resultHash = exec {
+      commandLine "git", "rev-parse", "--short", "HEAD"
+      standardOutput = hashStdOut
+      ignoreExitValue true
+    }
 
-  gitHash = hashStdOut.toString().trim()
-  gitBranch = branchStdOut.toString().trim()
+    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 }
 }
@@ -1046,47 +1061,110 @@ task cleanBuildingHTML(type: Delete) {
 }
 
 
-task convertBuildingMD(type: Exec) {
+def convertMdToHtml (FileTree mdFiles, File cssFile) {
+  MutableDataSet options = new MutableDataSet()
+
+  def extensions = new ArrayList<>()
+  extensions.add(AnchorLinkExtension.create()) 
+  extensions.add(AutolinkExtension.create())
+  extensions.add(StrikethroughExtension.create())
+  extensions.add(TaskListExtension.create())
+  extensions.add(TablesExtension.create())
+  extensions.add(TocExtension.create())
+  
+  options.set(Parser.EXTENSIONS, extensions)
+
+  // set GFM table parsing options
+  options.set(TablesExtension.WITH_CAPTION, false)
+  options.set(TablesExtension.COLUMN_SPANS, false)
+  options.set(TablesExtension.MIN_HEADER_ROWS, 1)
+  options.set(TablesExtension.MAX_HEADER_ROWS, 1)
+  options.set(TablesExtension.APPEND_MISSING_COLUMNS, true)
+  options.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true)
+  options.set(TablesExtension.HEADER_SEPARATOR_COLUMN_MATCH, true)
+  // GFM anchor links
+  options.set(AnchorLinkExtension.ANCHORLINKS_SET_ID, false)
+  options.set(AnchorLinkExtension.ANCHORLINKS_ANCHOR_CLASS, "anchor")
+  options.set(AnchorLinkExtension.ANCHORLINKS_SET_NAME, true)
+  options.set(AnchorLinkExtension.ANCHORLINKS_TEXT_PREFIX, "<span class=\"octicon octicon-link\"></span>")
+
+  Parser parser = Parser.builder(options).build()
+  HtmlRenderer renderer = HtmlRenderer.builder(options).build()
+
+  mdFiles.each { mdFile ->
+    // add table of contents
+    def mdText = "[TOC]\n"+mdFile.text
+
+    // grab the first top-level title
+    def title = null
+    def titleRegex = /(?m)^#(\s+|([^#]))(.*)/
+    def matcher = mdText =~ titleRegex
+    if (matcher.size() > 0) {
+      // matcher[0][2] is the first character of the title if there wasn't any whitespace after the #
+      title = (matcher[0][2] != null ? matcher[0][2] : "")+matcher[0][3]
+    }
+    // or use the filename if none found
+    if (title == null) {
+      title = mdFile.getName()
+    }
+
+    Node document = parser.parse(mdText)
+    String htmlBody = renderer.render(document)
+    def htmlText = '''<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">
+  <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" />
+'''
+    htmlText += ((title != null) ? "  <title>${title}</title>" : '' )
+    htmlText += '''
+    <style type="text/css">code{white-space: pre;}</style>
+'''
+    htmlText += ((cssFile != null) ? cssFile.text : '')
+    htmlText += '''</head>
+  <body>
+'''
+    htmlText += htmlBody
+    htmlText += '''
+  </body>
+</html>
+'''
+
+    def htmlFilePath = mdFile.getPath().replaceAll(/\..*?$/, ".html")
+    def htmlFile = file(htmlFilePath)
+    htmlFile.text = htmlText
+  }
+}
+
+
+task convertMdFiles {
   dependsOn cleanBuildingHTML
-  def buildingMD = "${jalviewDir}/${doc_dir}/building.md"
-  def css = "${jalviewDir}/${doc_dir}/github.css"
+  def mdFiles = fileTree(dir: "${jalviewDir}/${doc_dir}", include: "*.md")
+  def cssFile = file("${jalviewDir}/${flexmark_css}")
 
-  def pandoc = null
-  pandoc_exec.split(",").each {
-    if (file(it.trim()).exists()) {
-      pandoc = it.trim()
-      return true
-    }
+  doLast {
+    convertMdToHtml(mdFiles, cssFile)
   }
 
-  def buildtoolsPandoc = System.getProperty("user.home")+"/buildtools/pandoc/bin/pandoc"
-  if ((pandoc == null || ! file(pandoc).exists()) && file(buildtoolsPandoc).exists()) {
-    pandoc = System.getProperty("user.home")+"/buildtools/pandoc/bin/pandoc"
-  }
+  inputs.files(mdFiles)
+  inputs.file(cssFile)
 
-  doFirst {
-    if (pandoc != null && file(pandoc).exists()) {
-        commandLine pandoc, '-s', '-o', buildingHTML, '--metadata', 'pagetitle="Building Jalview from Source"', '--toc', '-H', css, buildingMD
-    } else {
-        println("Cannot find pandoc. Skipping convert building.md to HTML")
-        throw new StopExecutionException("Cannot find pandoc. Skipping convert building.md to HTML")
-    }
+  def htmlFiles = []
+  mdFiles.each { mdFile ->
+    def htmlFilePath = mdFile.getPath().replaceAll(/\..*?$/, ".html")
+    htmlFiles.add(file(htmlFilePath))
   }
-
-  ignoreExitValue true
-
-  inputs.file(buildingMD)
-  inputs.file(css)
-  outputs.file(buildingHTML)
+  outputs.files(htmlFiles)
 }
 
 
 task syncDocs(type: Sync) {
-  dependsOn convertBuildingMD
+  dependsOn convertMdFiles
   def syncDir = "${classesDir}/${doc_dir}"
   from fileTree("${jalviewDir}/${doc_dir}")
   into syncDir
-
 }
 
 
@@ -1309,6 +1387,7 @@ task cleanDist {
 
 shadowJar {
   group = "distribution"
+  description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
   if (buildDist) {
     dependsOn makeDist
   }
@@ -1783,7 +1862,11 @@ spotless {
 
 
 task sourceDist(type: Tar) {
+  group "distribution"
+  description "Create a source .tar.gz file for distribution"
   
+  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]
diff --git a/doc/JalviewJS-API.md b/doc/JalviewJS-API.md
new file mode 100644 (file)
index 0000000..64cae62
--- /dev/null
@@ -0,0 +1,123 @@
+# public interface JalviewJSApi
+
+## full list of available methods (from JalviewLiteJsApi):
+
+  public boolean addPdbFile(AlignFrame alFrame, String sequenceId, String pdbEntryString, String pdbFile);
+  public String getAlignment(String format);
+  public String getAlignment(String format, String suffix);
+  public String getAlignmentFrom(AlignFrame alf, String format);
+  public String getAlignmentFrom(AlignFrame alf, String format, String suffix);
+  public String getAlignmentOrder();
+  public String getAlignmentOrderFrom(AlignFrame alf);
+  public String getAlignmentOrderFrom(AlignFrame alf, String sep);
+  public String getAnnotation();
+  public String getAnnotationFrom(AlignFrame alf);
+  public Object getAppletParameter(String name, boolean asString);
+  public URL getCodeBase();
+  public URL getDocumentBase();
+  public String getFeatureGroups();
+  public String getFeatureGroupsOfState(boolean visible);
+  public String getFeatureGroupsOfStateOn(AlignFrame alf, boolean visible);
+  public String getFeatureGroupsOn(AlignFrame alf);
+  public String getFeatures(String format);
+  public String getFeaturesFrom(AlignFrame alf, String format);
+  public Object getFrameForSource(VamsasSource source);
+  public String getParameter(String name);
+  public String getSelectedSequences();
+  public String getSelectedSequences(String sep);
+  public String getSelectedSequencesAsAlignment(String format, String suffix);
+  public String getSelectedSequencesAsAlignmentFrom(AlignFrame alf, String format, String suffix);
+  public String getSelectedSequencesFrom(AlignFrame alf);
+  public String getSelectedSequencesFrom(AlignFrame alf, String sep);
+  public String getSeparator();
+  public AlignViewportI getViewport();
+  public void highlight(String sequenceId, String position, String alignedPosition);
+  public void highlightIn(AlignFrame alf, String sequenceId, String position, String alignedPosition);
+  public AlignFrame loadAlignment(String text, String title);
+  public void loadAnnotation(String annotation);
+  public void loadAnnotationFrom(AlignFrame alf, String annotation);
+  public void loadFeatures(String features, boolean autoenabledisplay);
+  public boolean loadFeaturesFrom(AlignFrame alf, String features, boolean autoenabledisplay);
+  public boolean loadScoreFile(String sScoreFile) throws IOException;
+  public void newFeatureSettings();
+  public void newStructureView(PDBEntry pdb, SequenceI[] seqs, String[] chains, DataSourceType protocol);
+  public Object openPcaPanel(AlignFrame af, String modelName);
+  public Object openTreePanel(AlignFrame af, String treeType, String modelName);
+  public String orderAlignmentBy(AlignFrame alf, String order, String undoName, String sep);
+  public String orderBy(String order, String undoName);
+  public String orderBy(String order, String undoName, String sep);
+  public Object parseArguments(String[] args);
+  public boolean parseFeaturesFile(String param, DataSourceType protocol);
+  public void removeSelectionListener(AlignFrame af, String listener);
+  public void scrollViewToColumnIn(AlignFrame alf, String leftHandColumn);
+  public void scrollViewToIn(AlignFrame alf, String topRow, String leftHandColumn);
+  public void scrollViewToRowIn(AlignFrame alf, String topRow);
+  public void select(String sequenceIds, String columns);
+  public void select(String sequenceIds, String columns, String sep);
+  public void selectIn(AlignFrame alf, String sequenceIds, String columns);
+  public void selectIn(AlignFrame alf, String sequenceIds, String columns, String sep);
+  public void setFeatureGroupState(String groups, boolean state);
+  public void setFeatureGroupState(String[] groups, boolean state);
+  public void setFeatureGroupStateOn(AlignFrame alf, String groups, boolean state);
+  public void setSelectionListener(AlignFrame af, String listener);
+  public void setSelectionListener(String listener);
+  public void setSeparator(String separator);
+  public void showOverview();
+  public void updateForAnnotations();
+
+## addition available methods (from JalviewLite):
+
+  public static String getBuildDate()
+  public static String getInstallation()
+  public static String getVersion()
+
+
+
+## proposed alias list:
+- remove overloaded methods
+- indicate null options
+- use standard arrays; no need for special separators
+- possibly return more actual objects, not just strings
+- moves AlignFrame to last parameter, as it is likely to be unnecessary
+
+public boolean addPdbFile(String sequenceId, String pdbId, String pdbFile, AlignFrame alFrame);
+public String getAlignment(String format, boolean addSuffix, AlignFrame alf);
+public String[] getAlignmentOrder(AlignFrame alf);
+public String getAnnotation(AlignFrame alf);
+public URL getCodeBase();
+public URL getDocumentBase();
+public String[] getFeatureGroups(AlignFrame alf);
+public String[] getFeatureGroupsOfState(boolean visible, AlignFrame alf);
+public String getFeatures(String format, AlignFrame alf);
+public String getParameter(String name);
+public Object getParameterAsObject(String name);
+public SequenceI[] getSelectedSequences(AlignFrame alf);
+public AlignFrame newView();
+public AlignFrame newView(String name);
+public AlignFrame newViewFrom(AlignFrame alf);
+public AlignFrame newViewFrom(AlignFrame alf, String name);
+public String getSelectedSequencesAsAlignment(String format, boolean addSuffix, AlignFrame alf);
+public void highlight(String sequenceId, String position, String alignedPosition, AlignFrame alf);
+public AlignFrame loadAlignment(String data, String title, int width, int height);
+public void loadAnnotation(String annotation, AlignFrame alf);
+public boolean loadFeatures(String features, boolean autoenabledisplay, AlignFrame alf);
+public boolean loadScoreFile(String sScoreFile, AlignFrame alf);
+public Object openPcaPanel(String modelName, AlignFrame alf);
+public Object openTreePanel(String treeType, String modelName, AlignFrame alf);
+public boolean orderAlignment(String[] ids, String undoName, AlignFrame alf);
+public Object parseArguments(String[] args);
+public void removeSelectionListener(String listener, AlignFrame af);
+public void scrollViewTo(int topRow, int leftHandColumn, AlignFrame alf);
+public void select(String[] sequenceIds, String[] columns, AlignFrame alf);
+public void setFeatureGroupState(String[] groups, boolean state, AlignFrame alf);
+public void setSelectionListener(String listener, AlignFrame alf);
+public void showOverview();
+public void showStructure(String pdbID, String fileType, AlignFrame alf);
+
+# unknown methods/shouldn't be in interface?
+
+  public Object getFrameForSource(VamsasSource source);
+  public void setSeparator(String separator);
+  
\ No newline at end of file
diff --git a/doc/JalviewJS-startupParams.md b/doc/JalviewJS-startupParams.md
new file mode 100644 (file)
index 0000000..9ff3352
--- /dev/null
@@ -0,0 +1,92 @@
+## JalviewJS startup parameters
+
+TODO -- go through these
+
+# parameters -- from jalview.bin.AppletParams
+
+  private final static String[] params = { 
+      "alignpdbfiles",
+      "ANNOTATIONCOLOUR_MAX", "ANNOTATIONCOLOUR_MIN",
+      "annotations",
+      "APPLICATION_URL", "automaticScrolling", "centrecolumnlabels",
+      "debug", "defaultColour", "defaultColourNuc", "defaultColourProt",
+      "embedded", "enableSplitFrame", "externalstructureviewer", "features",
+      "file", "file2", "format", "heightScale", "hidefeaturegroups",
+      "jalviewhelpurl", "jnetfile", "jpredfile", "label", "linkLabel_",
+      "linkLabel_1", "linkURL_", "nojmol", "normaliseLogo",
+      "normaliseSequenceLogo", "oninit", "PDBFILE", "PDBSEQ",
+      "relaxedidmatch", "resolvetocodebase", "RGB", "scaleProteinAsCdna",
+      "scoreFile", "separator", "sequence", "showAnnotation", "showbutton",
+      "showConsensus", "showConsensusHistogram", "showConservation",
+      "showfeaturegroups", "showFeatureSettings", "showFullId",
+      "showGroupConsensus", "showGroupConservation", "showOccupancy",
+      "showQuality", "showSequenceLogo", "showTreeBootstraps",
+      "showTreeDistances", "showUnconserved", "showUnlinkedTreeNodes",
+      "sortBy", "sortByTree", "tree", "treeFile", "upperCase",
+      "userDefinedColour", "widthScale", "windowHeight", "windowWidth",
+      "wrap", };
+
+
+
+
+# arguments -- from jalview.bin.ArgsParser
+
+  public static final String NOCALCULATION = "nocalculation";
+
+  public static final String NOMENUBAR = "nomenubar";
+
+  public static final String NOSTATUS = "nostatus";
+
+  public static final String SHOWOVERVIEW = "showoverview";
+
+  //
+  public static final String ANNOTATIONS = "annotations";
+
+  public static final String COLOUR = "colour";
+
+  public static final String FEATURES = "features";
+
+  public static final String GROOVY = "groovy";
+
+  public static final String GROUPS = "groups";
+
+  public static final String HEADLESS = "headless";
+
+  public static final String JABAWS = "jabaws";
+
+  public static final String NOANNOTATION = "no-annotation";
+
+  public static final String NOANNOTATION2 = "noannotation"; // BH 2019.05.07
+
+  public static final String NODISPLAY = "nodisplay";
+
+  public static final String NOGUI = "nogui";
+
+  public static final String NONEWS = "nonews";
+
+  public static final String NOQUESTIONNAIRE = "noquestionnaire";
+
+  public static final String NOSORTBYTREE = "nosortbytree";
+
+  public static final String NOUSAGESTATS = "nousagestats";
+
+  public static final String OPEN = "open";
+
+  public static final String OPEN2 = "open2"; // BH added -- for applet
+                                              // compatibility; not fully
+                                              // implemented
+
+  public static final String PROPS = "props";
+
+  public static final String QUESTIONNAIRE = "questionnaire";
+
+  public static final String SETPROP = "setprop";
+
+  public static final String SORTBYTREE = "sortbytree";
+
+  public static final String TREE = "tree";
+
+  public static final String VDOC = "vdoc";
+
+  public static final String VSESS = "vsess";
+
diff --git a/doc/JalviewJS-startupParams.xlsx b/doc/JalviewJS-startupParams.xlsx
new file mode 100644 (file)
index 0000000..d32fc48
Binary files /dev/null and b/doc/JalviewJS-startupParams.xlsx differ
index cc5eb30..2ca2641 100644 (file)
+<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">
-<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="pandoc" />
+  <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>
-  <style type="text/css">code{white-space: pre;}</style>
-  <style type="text/css">
-div.sourceCode { overflow-x: auto; }
-table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
-  margin: 0; padding: 0; vertical-align: baseline; border: none; }
-table.sourceCode { width: 100%; line-height: 100%; }
-td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
-td.sourceCode { padding-left: 5px; }
-code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
-code > span.dt { color: #902000; } /* DataType */
-code > span.dv { color: #40a070; } /* DecVal */
-code > span.bn { color: #40a070; } /* BaseN */
-code > span.fl { color: #40a070; } /* Float */
-code > span.ch { color: #4070a0; } /* Char */
-code > span.st { color: #4070a0; } /* String */
-code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
-code > span.ot { color: #007020; } /* Other */
-code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
-code > span.fu { color: #06287e; } /* Function */
-code > span.er { color: #ff0000; font-weight: bold; } /* Error */
-code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
-code > span.cn { color: #880000; } /* Constant */
-code > span.sc { color: #4070a0; } /* SpecialChar */
-code > span.vs { color: #4070a0; } /* VerbatimString */
-code > span.ss { color: #bb6688; } /* SpecialString */
-code > span.im { } /* Import */
-code > span.va { color: #19177c; } /* Variable */
-code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
-code > span.op { color: #666666; } /* Operator */
-code > span.bu { } /* BuiltIn */
-code > span.ex { } /* Extension */
-code > span.pp { color: #bc7a00; } /* Preprocessor */
-code > span.at { color: #7d9029; } /* Attribute */
-code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
-code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
-code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
-code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
-  </style>
-  <style>
-  @font-face {
-    font-family: octicons-link;
-    src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff');
-  }
-  
-  body {
-    -webkit-text-size-adjust: 100%;
-    text-size-adjust: 100%;
-    color: #333;
-    font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
-    font-size: 16px;
-    line-height: 1.6;
-    word-wrap: break-word;
-    width: 728px;
-    max-width: 99%;
-    box-sizing: border-box;
-    padding: 30px 30px 8rem 30px;
-    margin-left: auto;
-    margin-right: auto;
-  }
-  
-  body a {
-    background-color: transparent;
-  }
-  
-  body a:active,
-  body a:hover {
-    outline: 0;
-  }
-  
-  body strong {
-    font-weight: bold;
-  }
-  
-  body h1 {
-    font-size: 2em;
-    margin: 0.67em 0;
-  }
-  
-  body img {
-    border: 0;
-  }
-  
-  body hr {
-    box-sizing: content-box;
-    height: 0;
-  }
-  
-  body pre {
-    overflow: auto;
-  }
-  
-  body code,
-  body kbd,
-  body pre {
-    font-family: monospace, monospace;
-    font-size: 1em;
-  }
-  
-  body input {
-    color: inherit;
-    font: inherit;
-    margin: 0;
-  }
-  
-  body html input[disabled] {
-    cursor: default;
-  }
-  
-  body input {
-    line-height: normal;
-  }
-  
-  body input[type="checkbox"] {
-    box-sizing: border-box;
-    padding: 0;
-  }
-  
-  body table {
-    border-collapse: collapse;
-    border-spacing: 0;
-  }
-  
-  body td,
-  body th {
-    padding: 0;
-  }
-  
-  body * {
-    box-sizing: border-box;
-  }
-  
-  body input {
-    font: 13px / 1.4 Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
-  }
-  
-  body a {
-    color: #4078c0;
-    text-decoration: none;
-  }
-  
-  body a:hover,
-  body a:active {
-    text-decoration: underline;
-  }
-  
-  body hr {
-    height: 0;
-    margin: 15px 0;
-    overflow: hidden;
-    background: transparent;
-    border: 0;
-    border-bottom: 1px solid #ddd;
-  }
-  
-  body hr:before {
-    display: table;
-    content: "";
-  }
-  
-  body hr:after {
-    display: table;
-    clear: both;
-    content: "";
-  }
-  
-  body h1,
-  body h2,
-  body h3,
-  body h4,
-  body h5,
-  body h6 {
-    margin-top: 15px;
-    margin-bottom: 15px;
-    line-height: 1.1;
-  }
-  
-  body h1 {
-    font-size: 30px;
-  }
-  
-  body h2 {
-    font-size: 21px;
-  }
-  
-  body h3 {
-    font-size: 16px;
-  }
-  
-  body h4 {
-    font-size: 14px;
-  }
-  
-  body h5 {
-    font-size: 12px;
-  }
-  
-  body h6 {
-    font-size: 11px;
-  }
-  
-  body blockquote {
-    margin: 0;
-  }
-  
-  body ul,
-  body ol {
-    padding: 0;
-    margin-top: 0;
-    margin-bottom: 0;
-  }
-  
-  body ol ol,
-  body ul ol {
-    list-style-type: lower-roman;
-  }
-  
-  body ul ul ol,
-  body ul ol ol,
-  body ol ul ol,
-  body ol ol ol {
-    list-style-type: lower-alpha;
-  }
-  
-  body dd {
-    margin-left: 0;
-  }
-  
-  body code {
-    font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
-    font-size: 12px;
-  }
-  
-  body pre {
-    margin-top: 0;
-    margin-bottom: 0;
-    font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
-  }
-  
-  body .select::-ms-expand {
-    opacity: 0;
-  }
-  
-  body .octicon {
-    font: normal normal normal 16px/1 octicons-link;
-    display: inline-block;
-    text-decoration: none;
-    text-rendering: auto;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-    -webkit-user-select: none;
-    -moz-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-  }
-  
-  body .octicon-link:before {
-    content: '\f05c';
-  }
-  
-  body:before {
-    display: table;
-    content: "";
-  }
-  
-  body:after {
-    display: table;
-    clear: both;
-    content: "";
-  }
-  
-  body>*:first-child {
-    margin-top: 0 !important;
-  }
-  
-  body>*:last-child {
-    margin-bottom: 0 !important;
-  }
-  
-  body a:not([href]) {
-    color: inherit;
-    text-decoration: none;
-  }
-  
-  body .anchor {
-    display: inline-block;
-    padding-right: 2px;
-    margin-left: -18px;
-  }
-  
-  body .anchor:focus {
-    outline: none;
-  }
-  
-  body h1,
-  body h2,
-  body h3,
-  body h4,
-  body h5,
-  body h6 {
-    margin-top: 1em;
-    margin-bottom: 16px;
-    font-weight: bold;
-    line-height: 1.4;
-  }
-  
-  body h1 .octicon-link,
-  body h2 .octicon-link,
-  body h3 .octicon-link,
-  body h4 .octicon-link,
-  body h5 .octicon-link,
-  body h6 .octicon-link {
-    color: #000;
-    vertical-align: middle;
-    visibility: hidden;
-  }
-  
-  body h1:hover .anchor,
-  body h2:hover .anchor,
-  body h3:hover .anchor,
-  body h4:hover .anchor,
-  body h5:hover .anchor,
-  body h6:hover .anchor {
-    text-decoration: none;
-  }
-  
-  body h1:hover .anchor .octicon-link,
-  body h2:hover .anchor .octicon-link,
-  body h3:hover .anchor .octicon-link,
-  body h4:hover .anchor .octicon-link,
-  body h5:hover .anchor .octicon-link,
-  body h6:hover .anchor .octicon-link {
-    visibility: visible;
-  }
-  
-  body h1 {
-    padding-bottom: 0.3em;
-    font-size: 1.75em;
-    line-height: 1.2;
-  }
-  
-  body h1 .anchor {
-    line-height: 1;
-  }
-  
-  body h2 {
-    padding-bottom: 0.3em;
-    font-size: 1.5em;
-    line-height: 1.225;
-  }
-  
-  body h2 .anchor {
-    line-height: 1;
-  }
-  
-  body h3 {
-    font-size: 1.25em;
-    line-height: 1.43;
-  }
-  
-  body h3 .anchor {
-    line-height: 1.2;
-  }
-  
-  body h4 {
-    font-size: 1em;
-  }
-  
-  body h4 .anchor {
-    line-height: 1.2;
-  }
-  
-  body h5 {
-    font-size: 1em;
-  }
-  
-  body h5 .anchor {
-    line-height: 1.1;
-  }
-  
-  body h6 {
-    font-size: 1em;
-    color: #777;
-  }
-  
-  body h6 .anchor {
-    line-height: 1.1;
-  }
-  
-  body p,
-  body blockquote,
-  body ul,
-  body ol,
-  body dl,
-  body table,
-  body pre {
-    margin-top: 0;
-    margin-bottom: 16px;
-  }
-  
-  body hr {
-    height: 4px;
-    padding: 0;
-    margin: 16px 0;
-    background-color: #e7e7e7;
-    border: 0 none;
-  }
-  
-  body ul,
-  body ol {
-    padding-left: 2em;
-  }
-  
-  body ul ul,
-  body ul ol,
-  body ol ol,
-  body ol ul {
-    margin-top: 0;
-    margin-bottom: 0;
-  }
-  
-  body li>p {
-    margin-top: 16px;
-  }
-  
-  body dl {
-    padding: 0;
-  }
-  
-  body dl dt {
-    padding: 0;
-    margin-top: 16px;
-    font-size: 1em;
-    font-style: italic;
-    font-weight: bold;
-  }
-  
-  body dl dd {
-    padding: 0 16px;
-    margin-bottom: 16px;
-  }
-  
-  body blockquote {
-    padding: 0 15px;
-    color: #777;
-    border-left: 4px solid #ddd;
-  }
-  
-  body blockquote>:first-child {
-    margin-top: 0;
-  }
-  
-  body blockquote>:last-child {
-    margin-bottom: 0;
-  }
-  
-  body table {
-    display: block;
-    width: 100%;
-    overflow: auto;
-    word-break: normal;
-    word-break: keep-all;
-  }
-  
-  body table th {
-    font-weight: bold;
-  }
-  
-  body table th,
-  body table td {
-    padding: 6px 13px;
-    border: 1px solid #ddd;
-  }
-  
-  body table tr {
-    background-color: #fff;
-    border-top: 1px solid #ccc;
-  }
-  
-  body table tr:nth-child(2n) {
-    background-color: #f8f8f8;
-  }
-  
-  body img {
-    max-width: 100%;
-    box-sizing: content-box;
-    background-color: #fff;
-  }
-  
-  body code {
-    padding: 0;
-    padding-top: 0;
-    padding-bottom: 0;
-    margin: 0;
-    font-size: 85%;
-    background-color: rgba(0,0,0,0.04);
-    border-radius: 3px;
-  }
-  
-  body code:before,
-  body code:after {
-    letter-spacing: -0.2em;
-    content: "\00a0";
-  }
-  
-  body pre>code {
-    padding: 0;
-    margin: 0;
-    font-size: 100%;
-    word-break: normal;
-    white-space: pre;
-    background: transparent;
-    border: 0;
-  }
-  
-  body .highlight {
-    margin-bottom: 16px;
-  }
-  
-  body .highlight pre,
-  body pre {
-    padding: 16px;
-    overflow: auto;
-    font-size: 85%;
-    line-height: 1.45;
-    background-color: #f7f7f7;
-    border-radius: 3px;
-  }
-  
-  body .highlight pre {
-    margin-bottom: 0;
-    word-break: normal;
-  }
-  
-  body pre {
-    word-wrap: normal;
-  }
-  
-  body pre code {
-    display: inline;
-    max-width: initial;
-    padding: 0;
-    margin: 0;
-    overflow: initial;
-    line-height: inherit;
-    word-wrap: normal;
-    background-color: transparent;
-    border: 0;
-  }
-  
-  body pre code:before,
-  body pre code:after {
-    content: normal;
-  }
-  
-  body kbd {
-    display: inline-block;
-    padding: 3px 5px;
-    font-size: 11px;
-    line-height: 10px;
-    color: #555;
-    vertical-align: middle;
-    background-color: #fcfcfc;
-    border: solid 1px #ccc;
-    border-bottom-color: #bbb;
-    border-radius: 3px;
-    box-shadow: inset 0 -1px 0 #bbb;
-  }
-  
-  body .pl-c {
-    color: #969896;
-  }
-  
-  body .pl-c1,
-  body .pl-s .pl-v {
-    color: #0086b3;
-  }
-  
-  body .pl-e,
-  body .pl-en {
-    color: #795da3;
-  }
-  
-  body .pl-s .pl-s1,
-  body .pl-smi {
-    color: #333;
-  }
-  
-  body .pl-ent {
-    color: #63a35c;
-  }
-  
-  body .pl-k {
-    color: #a71d5d;
-  }
-  
-  body .pl-pds,
-  body .pl-s,
-  body .pl-s .pl-pse .pl-s1,
-  body .pl-sr,
-  body .pl-sr .pl-cce,
-  body .pl-sr .pl-sra,
-  body .pl-sr .pl-sre {
-    color: #183691;
-  }
-  
-  body .pl-v {
-    color: #ed6a43;
-  }
-  
-  body .pl-id {
-    color: #b52a1d;
-  }
-  
-  body .pl-ii {
-    background-color: #b52a1d;
-    color: #f8f8f8;
-  }
-  
-  body .pl-sr .pl-cce {
-    color: #63a35c;
-    font-weight: bold;
-  }
-  
-  body .pl-ml {
-    color: #693a17;
-  }
-  
-  body .pl-mh,
-  body .pl-mh .pl-en,
-  body .pl-ms {
-    color: #1d3e81;
-    font-weight: bold;
-  }
-  
-  body .pl-mq {
-    color: #008080;
-  }
-  
-  body .pl-mi {
-    color: #333;
-    font-style: italic;
-  }
-  
-  body .pl-mb {
-    color: #333;
-    font-weight: bold;
-  }
-  
-  body .pl-md {
-    background-color: #ffecec;
-    color: #bd2c00;
-  }
-  
-  body .pl-mi1 {
-    background-color: #eaffea;
-    color: #55a532;
-  }
-  
-  body .pl-mdr {
-    color: #795da3;
-    font-weight: bold;
-  }
-  
-  body .pl-mo {
-    color: #1d3e81;
-  }
-  
-  body kbd {
-    display: inline-block;
-    padding: 3px 5px;
-    font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
-    line-height: 10px;
-    color: #555;
-    vertical-align: middle;
-    background-color: #fcfcfc;
-    border: solid 1px #ccc;
-    border-bottom-color: #bbb;
-    border-radius: 3px;
-    box-shadow: inset 0 -1px 0 #bbb;
-  }
-  
-  body .task-list-item {
-    list-style-type: none;
-  }
-  
-  body .task-list-item+.task-list-item {
-    margin-top: 3px;
-  }
-  
-  body .task-list-item input {
-    margin: 0 0.35em 0.25em -1.6em;
-    vertical-align: middle;
-  }
-  
-  body :checked+.radio-label {
-    z-index: 1;
-    position: relative;
-    border-color: #4078c0;
-  }
-  </style>
+    <style type="text/css">code{white-space: pre;}</style>
+<style>
+@font-face {
+  font-family: octicons-link;
+  src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff');
+}
+
+body {
+  -webkit-text-size-adjust: 100%;
+  text-size-adjust: 100%;
+  color: #333;
+  font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+  font-size: 16px;
+  line-height: 1.6;
+  word-wrap: break-word;
+  width: 728px;
+  max-width: 99%;
+  box-sizing: border-box;
+  padding: 30px 30px 8rem 30px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+body a {
+  background-color: transparent;
+}
+
+body a:active,
+body a:hover {
+  outline: 0;
+}
+
+body strong {
+  font-weight: bold;
+}
+
+body h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+body img {
+  border: 0;
+}
+
+body hr {
+  box-sizing: content-box;
+  height: 0;
+}
+
+body pre {
+  overflow: auto;
+}
+
+body code,
+body kbd,
+body pre {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+
+body input {
+  color: inherit;
+  font: inherit;
+  margin: 0;
+}
+
+body html input[disabled] {
+  cursor: default;
+}
+
+body input {
+  line-height: normal;
+}
+
+body input[type="checkbox"] {
+  box-sizing: border-box;
+  padding: 0;
+}
+
+body table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+body td,
+body th {
+  padding: 0;
+}
+
+body * {
+  box-sizing: border-box;
+}
+
+body input {
+  font: 13px / 1.4 Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+}
+
+body a {
+  color: #4078c0;
+  text-decoration: none;
+}
+
+body a:hover,
+body a:active {
+  text-decoration: underline;
+}
+
+body hr {
+  height: 0;
+  margin: 15px 0;
+  overflow: hidden;
+  background: transparent;
+  border: 0;
+  border-bottom: 1px solid #ddd;
+}
+
+body hr:before {
+  display: table;
+  content: "";
+}
+
+body hr:after {
+  display: table;
+  clear: both;
+  content: "";
+}
+
+body h1,
+body h2,
+body h3,
+body h4,
+body h5,
+body h6 {
+  margin-top: 15px;
+  margin-bottom: 15px;
+  line-height: 1.1;
+}
+
+body h1 {
+  font-size: 30px;
+}
+
+body h2 {
+  font-size: 21px;
+}
+
+body h3 {
+  font-size: 16px;
+}
+
+body h4 {
+  font-size: 14px;
+}
+
+body h5 {
+  font-size: 12px;
+}
+
+body h6 {
+  font-size: 11px;
+}
+
+body blockquote {
+  margin: 0;
+}
+
+body ul,
+body ol {
+  padding: 0;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+body ol ol,
+body ul ol {
+  list-style-type: lower-roman;
+}
+
+body ul ul ol,
+body ul ol ol,
+body ol ul ol,
+body ol ol ol {
+  list-style-type: lower-alpha;
+}
+
+body dd {
+  margin-left: 0;
+}
+
+body code {
+  font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  font-size: 12px;
+}
+
+body pre {
+  margin-top: 0;
+  margin-bottom: 0;
+  font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
+}
+
+body .select::-ms-expand {
+  opacity: 0;
+}
+
+body .octicon {
+  font: normal normal normal 16px/1 octicons-link;
+  display: inline-block;
+  text-decoration: none;
+  text-rendering: auto;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+body .octicon-link:before {
+  content: '\f05c';
+}
+
+body:before {
+  display: table;
+  content: "";
+}
+
+body:after {
+  display: table;
+  clear: both;
+  content: "";
+}
+
+body>*:first-child {
+  margin-top: 0 !important;
+}
+
+body>*:last-child {
+  margin-bottom: 0 !important;
+}
+
+body a:not([href]) {
+  color: inherit;
+  text-decoration: none;
+}
+
+body .anchor {
+  display: inline-block;
+  padding-right: 2px;
+  margin-left: -18px;
+}
+
+body .anchor:focus {
+  outline: none;
+}
+
+body h1,
+body h2,
+body h3,
+body h4,
+body h5,
+body h6 {
+  margin-top: 1em;
+  margin-bottom: 16px;
+  font-weight: bold;
+  line-height: 1.4;
+}
+
+body h1 .octicon-link,
+body h2 .octicon-link,
+body h3 .octicon-link,
+body h4 .octicon-link,
+body h5 .octicon-link,
+body h6 .octicon-link {
+  color: #000;
+  vertical-align: middle;
+  visibility: hidden;
+}
+
+body h1:hover .anchor,
+body h2:hover .anchor,
+body h3:hover .anchor,
+body h4:hover .anchor,
+body h5:hover .anchor,
+body h6:hover .anchor {
+  text-decoration: none;
+}
+
+body h1:hover .anchor .octicon-link,
+body h2:hover .anchor .octicon-link,
+body h3:hover .anchor .octicon-link,
+body h4:hover .anchor .octicon-link,
+body h5:hover .anchor .octicon-link,
+body h6:hover .anchor .octicon-link {
+  visibility: visible;
+}
+
+body h1 {
+  padding-bottom: 0.3em;
+  font-size: 1.75em;
+  line-height: 1.2;
+}
+
+body h1 .anchor {
+  line-height: 1;
+}
+
+body h2 {
+  padding-bottom: 0.3em;
+  font-size: 1.5em;
+  line-height: 1.225;
+}
+
+body h2 .anchor {
+  line-height: 1;
+}
+
+body h3 {
+  font-size: 1.25em;
+  line-height: 1.43;
+}
+
+body h3 .anchor {
+  line-height: 1.2;
+}
+
+body h4 {
+  font-size: 1em;
+}
+
+body h4 .anchor {
+  line-height: 1.2;
+}
+
+body h5 {
+  font-size: 1em;
+}
+
+body h5 .anchor {
+  line-height: 1.1;
+}
+
+body h6 {
+  font-size: 1em;
+  color: #777;
+}
+
+body h6 .anchor {
+  line-height: 1.1;
+}
+
+body p,
+body blockquote,
+body ul,
+body ol,
+body dl,
+body table,
+body pre {
+  margin-top: 0;
+  margin-bottom: 16px;
+}
+
+body hr {
+  height: 4px;
+  padding: 0;
+  margin: 16px 0;
+  background-color: #e7e7e7;
+  border: 0 none;
+}
+
+body ul,
+body ol {
+  padding-left: 2em;
+}
+
+body ul ul,
+body ul ol,
+body ol ol,
+body ol ul {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+body li>p {
+  margin-top: 16px;
+}
+
+body dl {
+  padding: 0;
+}
+
+body dl dt {
+  padding: 0;
+  margin-top: 16px;
+  font-size: 1em;
+  font-style: italic;
+  font-weight: bold;
+}
+
+body dl dd {
+  padding: 0 16px;
+  margin-bottom: 16px;
+}
+
+body blockquote {
+  padding: 0 15px;
+  color: #777;
+  border-left: 4px solid #ddd;
+}
+
+body blockquote>:first-child {
+  margin-top: 0;
+}
+
+body blockquote>:last-child {
+  margin-bottom: 0;
+}
+
+body table {
+  display: block;
+  width: 100%;
+  overflow: auto;
+  word-break: normal;
+  word-break: keep-all;
+}
+
+body table th {
+  font-weight: bold;
+}
+
+body table th,
+body table td {
+  padding: 6px 13px;
+  border: 1px solid #ddd;
+}
+
+body table tr {
+  background-color: #fff;
+  border-top: 1px solid #ccc;
+}
+
+body table tr:nth-child(2n) {
+  background-color: #f8f8f8;
+}
+
+body img {
+  max-width: 100%;
+  box-sizing: content-box;
+  background-color: #fff;
+}
+
+body code {
+  padding: 0;
+  padding-top: 0;
+  padding-bottom: 0;
+  margin: 0;
+  font-size: 85%;
+  background-color: rgba(0,0,0,0.04);
+  border-radius: 3px;
+}
+
+body code:before,
+body code:after {
+  letter-spacing: -0.2em;
+  content: "\00a0";
+}
+
+body pre>code {
+  padding: 0;
+  margin: 0;
+  font-size: 100%;
+  word-break: normal;
+  white-space: pre;
+  background: transparent;
+  border: 0;
+}
+
+body .highlight {
+  margin-bottom: 16px;
+}
+
+body .highlight pre,
+body pre {
+  padding: 16px;
+  overflow: auto;
+  font-size: 85%;
+  line-height: 1.45;
+  background-color: #f7f7f7;
+  border-radius: 3px;
+}
+
+body .highlight pre {
+  margin-bottom: 0;
+  word-break: normal;
+}
+
+body pre {
+  word-wrap: normal;
+}
+
+body pre code {
+  display: inline;
+  max-width: initial;
+  padding: 0;
+  margin: 0;
+  overflow: initial;
+  line-height: inherit;
+  word-wrap: normal;
+  background-color: transparent;
+  border: 0;
+}
+
+body pre code:before,
+body pre code:after {
+  content: normal;
+}
+
+body kbd {
+  display: inline-block;
+  padding: 3px 5px;
+  font-size: 11px;
+  line-height: 10px;
+  color: #555;
+  vertical-align: middle;
+  background-color: #fcfcfc;
+  border: solid 1px #ccc;
+  border-bottom-color: #bbb;
+  border-radius: 3px;
+  box-shadow: inset 0 -1px 0 #bbb;
+}
+
+body .pl-c {
+  color: #969896;
+}
+
+body .pl-c1,
+body .pl-s .pl-v {
+  color: #0086b3;
+}
+
+body .pl-e,
+body .pl-en {
+  color: #795da3;
+}
+
+body .pl-s .pl-s1,
+body .pl-smi {
+  color: #333;
+}
+
+body .pl-ent {
+  color: #63a35c;
+}
+
+body .pl-k {
+  color: #a71d5d;
+}
+
+body .pl-pds,
+body .pl-s,
+body .pl-s .pl-pse .pl-s1,
+body .pl-sr,
+body .pl-sr .pl-cce,
+body .pl-sr .pl-sra,
+body .pl-sr .pl-sre {
+  color: #183691;
+}
+
+body .pl-v {
+  color: #ed6a43;
+}
+
+body .pl-id {
+  color: #b52a1d;
+}
+
+body .pl-ii {
+  background-color: #b52a1d;
+  color: #f8f8f8;
+}
+
+body .pl-sr .pl-cce {
+  color: #63a35c;
+  font-weight: bold;
+}
+
+body .pl-ml {
+  color: #693a17;
+}
+
+body .pl-mh,
+body .pl-mh .pl-en,
+body .pl-ms {
+  color: #1d3e81;
+  font-weight: bold;
+}
+
+body .pl-mq {
+  color: #008080;
+}
+
+body .pl-mi {
+  color: #333;
+  font-style: italic;
+}
+
+body .pl-mb {
+  color: #333;
+  font-weight: bold;
+}
+
+body .pl-md {
+  background-color: #ffecec;
+  color: #bd2c00;
+}
+
+body .pl-mi1 {
+  background-color: #eaffea;
+  color: #55a532;
+}
+
+body .pl-mdr {
+  color: #795da3;
+  font-weight: bold;
+}
+
+body .pl-mo {
+  color: #1d3e81;
+}
+
+body kbd {
+  display: inline-block;
+  padding: 3px 5px;
+  font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  line-height: 10px;
+  color: #555;
+  vertical-align: middle;
+  background-color: #fcfcfc;
+  border: solid 1px #ccc;
+  border-bottom-color: #bbb;
+  border-radius: 3px;
+  box-shadow: inset 0 -1px 0 #bbb;
+}
+
+body .task-list-item {
+  list-style-type: none;
+}
+
+body .task-list-item+.task-list-item {
+  margin-top: 3px;
+}
+
+body .task-list-item input {
+  margin: 0 0.35em 0.25em -1.6em;
+  vertical-align: middle;
+}
+
+body :checked+.radio-label {
+  z-index: 1;
+  position: relative;
+  border-color: #4078c0;
+}
+</style>
 </head>
-<body>
-<div id="TOC">
+  <body>
 <ul>
-<li><a href="#building-jalview-from-source">Building Jalview from Source</a><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>
+<ul>
 <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>
+</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>
-</ul></li>
-<li><a href="#building-jalview">Building Jalview</a><ul>
+</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>
@@ -726,18 +693,19 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 <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>
+</ul>
+</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>
+<li><a href="#setting-up-in-eclipse-ide">Setting up in Eclipse IDE</a>
+<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>
-</ul></li>
-</ul></li>
 </ul>
-</div>
-<h1 id="building-jalview-from-source">Building Jalview from Source</h1>
-<h2 id="tldr">tl;dr</h2>
+</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>
 <pre><code># download
 git clone http://source.jalview.org/git/jalview.git
 # compile
@@ -750,411 +718,504 @@ java -jar build/libs/jalview-all-11.jar
 gradle getdown
 # use launcher
 cd getdown/files
-java -jar getdown-launcher.jar . jalview</code></pre>
-<h2 id="setting-up">Setting up</h2>
+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>
 <blockquote>
-<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>
+<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>
 </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>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">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>
+<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>
 <blockquote>
-<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>
+<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>
 <pre><code>brew tap adoptopenjdk/openjdk
-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>
+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>
 </blockquote>
-<h3 id="gradle-and-git">gradle and git</h3>
+<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>
 <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">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>
+<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>
 <p>or if you aready have them installed but need to upgrade the version:</p>
-<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>
+<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>
 <p>this will depend on which distribution you're using.</p>
-<h5 id="for-debian-based-distributions-e.g.-mint-ubuntu-debian">For <em>Debian</em> based distributions (e.g. Mint, Ubuntu, Debian)</h5>
+<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>
 <p>run</p>
-<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>
+<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>
 <p>run</p>
-<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> yum install gradle git</code></pre></div>
+<pre><code class="language-bash">sudo yum install gradle git
+</code></pre>
 <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">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>
+<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>
 <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 class="header">
-<th>dir/ or file</th>
-<th>contains</th>
-</tr>
+<tr><th>dir/ or file</th><th>contains</th></tr>
 </thead>
 <tbody>
-<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>
+<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>
 </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">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>
+<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>
 <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">Minimal Jalview Build</h3>
+<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>
 <p>To compile the necessary class files, just run</p>
-<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>
+<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>
 <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">Jalview in a Jar File</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"><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>To package the <code>classes</code>, <code>resources</code>, and <code>help</code> into one jar, you can run</p>
-<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> jar</code></pre></div>
+<pre><code class="language-bash">gradle jar
+</code></pre>
 <p>which assembles the Jalview classes and resources into <code>dist/jalview.jar</code></p>
 <p>To run this, use</p>
-<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>
+<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>
 <p>which puts all required jar files into <code>dist</code> so you can run with</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>
+<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/lib/jalview-all-11.jar</code> with</p>
-<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> shadowJar</code></pre></div>
+<pre><code class="language-bash">gradle shadowJar
+</code></pre>
 <p>and run it with</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>
+<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>
 <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">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>
+<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>
 <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>
-<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>
+<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>
 </blockquote>
 <p>To assemble Jalview with <code>getdown</code> use the following gradle task:</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>
+<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>
 <p>This can be launched with</p>
-<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>
+<pre><code class="language-bash">java -jar getdown/files/11/getdown-launcher.jar getdown/files/11/ jalview
+</code></pre>
 <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">Running tests</h3>
+<h3 id="running-tests"><a href="#running-tests" name="running-tests" class="anchor"><span class="octicon octicon-link"></span>Running tests</a></h3>
 <p>There are substantial tests written for Jalview that use TestNG, which you can run with</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>
+<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>
 <p>You can run different defined groups of tests with</p>
-<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> test -PtestngGroups=Network</code></pre></div>
+<pre><code class="language-bash">gradle test -PtestngGroups=Network
+</code></pre>
 <p>Available groups include Functional (default), Network, External.</p>
-<h4 id="excluding-some-tests">Excluding some tests</h4>
+<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>
 <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>
-<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>
+<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>
 <p>Valid values are <code>11</code> and <code>1.8</code>.</p>
 <p>e.g.</p>
-<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> shadowJar -PJAVA_VERSION=1.8</code></pre></div>
+<pre><code class="language-bash">gradle shadowJar -PJAVA_VERSION=1.8
+</code></pre>
 <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"><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>
+<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>
 <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>
-<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>
+<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>
 <p>The default value is all of them.</p>
 <p>e.g.</p>
-<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>
+<pre><code class="language-bash">gradle installers -PJAVA_VERSION=1.8 -Pinstall4jMediaTypes=macosArchive
+</code></pre>
 <p>To get an up-to-date list of possible values, you can run</p>
-<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>
+<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>
 <p>in the <code>jalview</code> root folder.</p>
-<h2 id="enabling-code-coverage-with-openclover">Enabling Code Coverage with OpenClover</h2>
+<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>
 <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">Troubleshooting report generation</h4>
+<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>
 <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">JVM Memory settings - increase if out of memory errors are reported</h5>
+<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>
 <p><code>cloverReportJVMHeap = 2g</code></p>
-<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>
+<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>
 <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">Add -v to debug velocity html generation errors, or -d to track more detailed issues with the coverage database</h5>
+<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>
 <p><code>cloverReportHTMLOptions =</code></p>
-<h5 id="v-for-verbose--d-for-debug-level-messages-as-above">-v for verbose, -d for debug level messages (as above)</h5>
+<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>
 <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">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>
+<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>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 style="list-style-type: decimal">
+<ol>
 <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 style="list-style-type: decimal">
+<ol>
 <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>
+<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">Importing Jalview as an Eclipse project</h3>
-<h4 id="importing-an-already-downloaded-git-repo">Importing an already downloaded git repo</h4>
+<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>
 <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>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">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>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>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. &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>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>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>. &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>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>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>
-<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><img src="./images/eclipse_add_gradle_nature.png" alt="Eclipse Add Gradle Nature" /></p>
 <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">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>
+<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>
 <hr />
 <p><a href="mailto:help@jalview.org">Jalview Development Team</a></p>
-</body>
+
+  </body>
 </html>
index ad19979..06e796d 100644 (file)
@@ -563,18 +563,18 @@ Search for and install:
 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
 >
-Help->Install New Software...
+> Help->Install New Software...
 >
-and entering
+> and entering
 >
-`TestNG Release - https://dl.bintray.com/testng-team/testng-eclipse-release`
+> `TestNG Release - https://dl.bintray.com/testng-team/testng-eclipse-release`
 >
-into the *Work with* box and click on the *Add...* button.
+> into the *Work with* box and click on the *Add...* button.
 >
-Eclipse might pause for a bit with the word *Pending* in the table below at this point, but it will eventually list TestNG with 
+> Eclipse might pause for a bit with the word *Pending* in the table below at this point, but it will eventually list TestNG with 
 a selection box under the *Name* column.
 >
-Select *TestNG* and carry on through the 
+> Select *TestNG* and carry on through the 
 install process to install the TestNG plugin.
 
 After installing the plugins, check that Java 11 is set up in Eclipse as the default JRE (see section [Java 11 compliant JDK](#java-11-compliant-jdk)).
diff --git a/doc/releaseprocess.html b/doc/releaseprocess.html
new file mode 100644 (file)
index 0000000..7205420
--- /dev/null
@@ -0,0 +1,692 @@
+<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">
+  <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>Making a Jalview release</title>
+    <style type="text/css">code{white-space: pre;}</style>
+<style>
+@font-face {
+  font-family: octicons-link;
+  src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff');
+}
+
+body {
+  -webkit-text-size-adjust: 100%;
+  text-size-adjust: 100%;
+  color: #333;
+  font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+  font-size: 16px;
+  line-height: 1.6;
+  word-wrap: break-word;
+  width: 728px;
+  max-width: 99%;
+  box-sizing: border-box;
+  padding: 30px 30px 8rem 30px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+body a {
+  background-color: transparent;
+}
+
+body a:active,
+body a:hover {
+  outline: 0;
+}
+
+body strong {
+  font-weight: bold;
+}
+
+body h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+body img {
+  border: 0;
+}
+
+body hr {
+  box-sizing: content-box;
+  height: 0;
+}
+
+body pre {
+  overflow: auto;
+}
+
+body code,
+body kbd,
+body pre {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+
+body input {
+  color: inherit;
+  font: inherit;
+  margin: 0;
+}
+
+body html input[disabled] {
+  cursor: default;
+}
+
+body input {
+  line-height: normal;
+}
+
+body input[type="checkbox"] {
+  box-sizing: border-box;
+  padding: 0;
+}
+
+body table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+body td,
+body th {
+  padding: 0;
+}
+
+body * {
+  box-sizing: border-box;
+}
+
+body input {
+  font: 13px / 1.4 Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+}
+
+body a {
+  color: #4078c0;
+  text-decoration: none;
+}
+
+body a:hover,
+body a:active {
+  text-decoration: underline;
+}
+
+body hr {
+  height: 0;
+  margin: 15px 0;
+  overflow: hidden;
+  background: transparent;
+  border: 0;
+  border-bottom: 1px solid #ddd;
+}
+
+body hr:before {
+  display: table;
+  content: "";
+}
+
+body hr:after {
+  display: table;
+  clear: both;
+  content: "";
+}
+
+body h1,
+body h2,
+body h3,
+body h4,
+body h5,
+body h6 {
+  margin-top: 15px;
+  margin-bottom: 15px;
+  line-height: 1.1;
+}
+
+body h1 {
+  font-size: 30px;
+}
+
+body h2 {
+  font-size: 21px;
+}
+
+body h3 {
+  font-size: 16px;
+}
+
+body h4 {
+  font-size: 14px;
+}
+
+body h5 {
+  font-size: 12px;
+}
+
+body h6 {
+  font-size: 11px;
+}
+
+body blockquote {
+  margin: 0;
+}
+
+body ul,
+body ol {
+  padding: 0;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+body ol ol,
+body ul ol {
+  list-style-type: lower-roman;
+}
+
+body ul ul ol,
+body ul ol ol,
+body ol ul ol,
+body ol ol ol {
+  list-style-type: lower-alpha;
+}
+
+body dd {
+  margin-left: 0;
+}
+
+body code {
+  font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  font-size: 12px;
+}
+
+body pre {
+  margin-top: 0;
+  margin-bottom: 0;
+  font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
+}
+
+body .select::-ms-expand {
+  opacity: 0;
+}
+
+body .octicon {
+  font: normal normal normal 16px/1 octicons-link;
+  display: inline-block;
+  text-decoration: none;
+  text-rendering: auto;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+body .octicon-link:before {
+  content: '\f05c';
+}
+
+body:before {
+  display: table;
+  content: "";
+}
+
+body:after {
+  display: table;
+  clear: both;
+  content: "";
+}
+
+body>*:first-child {
+  margin-top: 0 !important;
+}
+
+body>*:last-child {
+  margin-bottom: 0 !important;
+}
+
+body a:not([href]) {
+  color: inherit;
+  text-decoration: none;
+}
+
+body .anchor {
+  display: inline-block;
+  padding-right: 2px;
+  margin-left: -18px;
+}
+
+body .anchor:focus {
+  outline: none;
+}
+
+body h1,
+body h2,
+body h3,
+body h4,
+body h5,
+body h6 {
+  margin-top: 1em;
+  margin-bottom: 16px;
+  font-weight: bold;
+  line-height: 1.4;
+}
+
+body h1 .octicon-link,
+body h2 .octicon-link,
+body h3 .octicon-link,
+body h4 .octicon-link,
+body h5 .octicon-link,
+body h6 .octicon-link {
+  color: #000;
+  vertical-align: middle;
+  visibility: hidden;
+}
+
+body h1:hover .anchor,
+body h2:hover .anchor,
+body h3:hover .anchor,
+body h4:hover .anchor,
+body h5:hover .anchor,
+body h6:hover .anchor {
+  text-decoration: none;
+}
+
+body h1:hover .anchor .octicon-link,
+body h2:hover .anchor .octicon-link,
+body h3:hover .anchor .octicon-link,
+body h4:hover .anchor .octicon-link,
+body h5:hover .anchor .octicon-link,
+body h6:hover .anchor .octicon-link {
+  visibility: visible;
+}
+
+body h1 {
+  padding-bottom: 0.3em;
+  font-size: 1.75em;
+  line-height: 1.2;
+}
+
+body h1 .anchor {
+  line-height: 1;
+}
+
+body h2 {
+  padding-bottom: 0.3em;
+  font-size: 1.5em;
+  line-height: 1.225;
+}
+
+body h2 .anchor {
+  line-height: 1;
+}
+
+body h3 {
+  font-size: 1.25em;
+  line-height: 1.43;
+}
+
+body h3 .anchor {
+  line-height: 1.2;
+}
+
+body h4 {
+  font-size: 1em;
+}
+
+body h4 .anchor {
+  line-height: 1.2;
+}
+
+body h5 {
+  font-size: 1em;
+}
+
+body h5 .anchor {
+  line-height: 1.1;
+}
+
+body h6 {
+  font-size: 1em;
+  color: #777;
+}
+
+body h6 .anchor {
+  line-height: 1.1;
+}
+
+body p,
+body blockquote,
+body ul,
+body ol,
+body dl,
+body table,
+body pre {
+  margin-top: 0;
+  margin-bottom: 16px;
+}
+
+body hr {
+  height: 4px;
+  padding: 0;
+  margin: 16px 0;
+  background-color: #e7e7e7;
+  border: 0 none;
+}
+
+body ul,
+body ol {
+  padding-left: 2em;
+}
+
+body ul ul,
+body ul ol,
+body ol ol,
+body ol ul {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+body li>p {
+  margin-top: 16px;
+}
+
+body dl {
+  padding: 0;
+}
+
+body dl dt {
+  padding: 0;
+  margin-top: 16px;
+  font-size: 1em;
+  font-style: italic;
+  font-weight: bold;
+}
+
+body dl dd {
+  padding: 0 16px;
+  margin-bottom: 16px;
+}
+
+body blockquote {
+  padding: 0 15px;
+  color: #777;
+  border-left: 4px solid #ddd;
+}
+
+body blockquote>:first-child {
+  margin-top: 0;
+}
+
+body blockquote>:last-child {
+  margin-bottom: 0;
+}
+
+body table {
+  display: block;
+  width: 100%;
+  overflow: auto;
+  word-break: normal;
+  word-break: keep-all;
+}
+
+body table th {
+  font-weight: bold;
+}
+
+body table th,
+body table td {
+  padding: 6px 13px;
+  border: 1px solid #ddd;
+}
+
+body table tr {
+  background-color: #fff;
+  border-top: 1px solid #ccc;
+}
+
+body table tr:nth-child(2n) {
+  background-color: #f8f8f8;
+}
+
+body img {
+  max-width: 100%;
+  box-sizing: content-box;
+  background-color: #fff;
+}
+
+body code {
+  padding: 0;
+  padding-top: 0;
+  padding-bottom: 0;
+  margin: 0;
+  font-size: 85%;
+  background-color: rgba(0,0,0,0.04);
+  border-radius: 3px;
+}
+
+body code:before,
+body code:after {
+  letter-spacing: -0.2em;
+  content: "\00a0";
+}
+
+body pre>code {
+  padding: 0;
+  margin: 0;
+  font-size: 100%;
+  word-break: normal;
+  white-space: pre;
+  background: transparent;
+  border: 0;
+}
+
+body .highlight {
+  margin-bottom: 16px;
+}
+
+body .highlight pre,
+body pre {
+  padding: 16px;
+  overflow: auto;
+  font-size: 85%;
+  line-height: 1.45;
+  background-color: #f7f7f7;
+  border-radius: 3px;
+}
+
+body .highlight pre {
+  margin-bottom: 0;
+  word-break: normal;
+}
+
+body pre {
+  word-wrap: normal;
+}
+
+body pre code {
+  display: inline;
+  max-width: initial;
+  padding: 0;
+  margin: 0;
+  overflow: initial;
+  line-height: inherit;
+  word-wrap: normal;
+  background-color: transparent;
+  border: 0;
+}
+
+body pre code:before,
+body pre code:after {
+  content: normal;
+}
+
+body kbd {
+  display: inline-block;
+  padding: 3px 5px;
+  font-size: 11px;
+  line-height: 10px;
+  color: #555;
+  vertical-align: middle;
+  background-color: #fcfcfc;
+  border: solid 1px #ccc;
+  border-bottom-color: #bbb;
+  border-radius: 3px;
+  box-shadow: inset 0 -1px 0 #bbb;
+}
+
+body .pl-c {
+  color: #969896;
+}
+
+body .pl-c1,
+body .pl-s .pl-v {
+  color: #0086b3;
+}
+
+body .pl-e,
+body .pl-en {
+  color: #795da3;
+}
+
+body .pl-s .pl-s1,
+body .pl-smi {
+  color: #333;
+}
+
+body .pl-ent {
+  color: #63a35c;
+}
+
+body .pl-k {
+  color: #a71d5d;
+}
+
+body .pl-pds,
+body .pl-s,
+body .pl-s .pl-pse .pl-s1,
+body .pl-sr,
+body .pl-sr .pl-cce,
+body .pl-sr .pl-sra,
+body .pl-sr .pl-sre {
+  color: #183691;
+}
+
+body .pl-v {
+  color: #ed6a43;
+}
+
+body .pl-id {
+  color: #b52a1d;
+}
+
+body .pl-ii {
+  background-color: #b52a1d;
+  color: #f8f8f8;
+}
+
+body .pl-sr .pl-cce {
+  color: #63a35c;
+  font-weight: bold;
+}
+
+body .pl-ml {
+  color: #693a17;
+}
+
+body .pl-mh,
+body .pl-mh .pl-en,
+body .pl-ms {
+  color: #1d3e81;
+  font-weight: bold;
+}
+
+body .pl-mq {
+  color: #008080;
+}
+
+body .pl-mi {
+  color: #333;
+  font-style: italic;
+}
+
+body .pl-mb {
+  color: #333;
+  font-weight: bold;
+}
+
+body .pl-md {
+  background-color: #ffecec;
+  color: #bd2c00;
+}
+
+body .pl-mi1 {
+  background-color: #eaffea;
+  color: #55a532;
+}
+
+body .pl-mdr {
+  color: #795da3;
+  font-weight: bold;
+}
+
+body .pl-mo {
+  color: #1d3e81;
+}
+
+body kbd {
+  display: inline-block;
+  padding: 3px 5px;
+  font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  line-height: 10px;
+  color: #555;
+  vertical-align: middle;
+  background-color: #fcfcfc;
+  border: solid 1px #ccc;
+  border-bottom-color: #bbb;
+  border-radius: 3px;
+  box-shadow: inset 0 -1px 0 #bbb;
+}
+
+body .task-list-item {
+  list-style-type: none;
+}
+
+body .task-list-item+.task-list-item {
+  margin-top: 3px;
+}
+
+body .task-list-item input {
+  margin: 0 0.35em 0.25em -1.6em;
+  vertical-align: middle;
+}
+
+body :checked+.radio-label {
+  z-index: 1;
+  position: relative;
+  border-color: #4078c0;
+}
+</style>
+</head>
+  <body>
+<ul>
+<li><a href="#objectives">Objectives</a></li>
+<li><a href="#tldr">tl;dr</a></li>
+</ul>
+<h1 id="making-a-jalview-release"><a href="#making-a-jalview-release" name="making-a-jalview-release" class="anchor"><span class="octicon octicon-link"></span>Making a Jalview release</a></h1>
+<h2 id="objectives"><a href="#objectives" name="objectives" class="anchor"><span class="octicon octicon-link"></span>Objectives</a></h2>
+<p>1 Update the release getdown channel
+2 Update single executable jar
+3 Update installer
+4 Update web site with release notes
+5 Update Conda recipe
+6 Update Homebrew
+7 Update JalviewJS</p>
+<h2 id="tldr"><a href="#tldr" name="tldr" class="anchor"><span class="octicon octicon-link"></span>tl;dr</a></h2>
+<p><code>OSX_SIGNING=yes KEYPASS=MYSECRETKEY gradle -POSX_KEYPASS=true -POSX_KEYSTORE=/Users/jbb/buildtools/private/sourceofpain.p12 -PJSIGN_SH=&quot;/Users/jbb/buildtools/jsign.sh&quot; -Pinstall4j_verbose=true -PJAVA_VERSION=1.8 -PCHANNEL=RELEASE -PVERSION=W.X.Y.Z getdown installers shadowJar</code>
+<code>codesign -s &quot;MY Apple Dev Name&quot; build/install4j/1.8/Jalview-DEVELOPMENT-macos-java_8.dmg</code></p>
+
+  </body>
+</html>
index 745e8c7..b99cdca 100644 (file)
@@ -110,6 +110,8 @@ j11libDir = j11lib
 j11modDir = j11mod
 j11modules = com.sun.istack.runtime,com.sun.xml.bind,com.sun.xml.fastinfoset,com.sun.xml.streambuffer,com.sun.xml.txw2,com.sun.xml.ws.policy,java.activation,java.annotation,java.base,java.compiler,java.datatransfer,java.desktop,java.logging,java.management,java.management.rmi,java.naming,java.prefs,java.rmi,java.scripting,java.security.sasl,java.sql,java.xml,java.xml.bind,java.xml.soap,java.xml.ws,javax.jws,jdk.httpserver,jdk.jsobject,jdk.unsupported,jdk.xml.dom,org.jvnet.mimepull,org.jvnet.staxex,javax.servlet.api,java.ws.rs
 
+flexmark_css = utils/doc/github.css
+
 install4j_home_dir = ~/buildtools/install4j8
 install4j_copyright_message = ...
 install4j_bundle_id = org.jalview.jalview-desktop
@@ -128,9 +130,6 @@ OSX_KEYSTORE =
 OSX_KEYPASS =
 JSIGN_SH = echo
 
-pandoc_exec = /usr/local/bin/pandoc,/usr/bin/pandoc
-dev = false
-
 CHANNEL=LOCAL
 getdown_channel_base = https://www.jalview.org/getdown
 getdown_app_dir_release = release
diff --git a/j11lib/Jmol-14.29.17-no_netscape.jar b/j11lib/Jmol-14.29.17-no_netscape.jar
deleted file mode 100644 (file)
index 5e7d573..0000000
Binary files a/j11lib/Jmol-14.29.17-no_netscape.jar and /dev/null differ
diff --git a/j11lib/Jmol-15.1.3.jar b/j11lib/Jmol-15.1.3.jar
new file mode 100644 (file)
index 0000000..1672e67
Binary files /dev/null and b/j11lib/Jmol-15.1.3.jar differ
diff --git a/j8lib/Jmol-14.29.17.jar b/j8lib/Jmol-14.29.17.jar
deleted file mode 100644 (file)
index 136eb93..0000000
Binary files a/j8lib/Jmol-14.29.17.jar and /dev/null differ
diff --git a/j8lib/Jmol-15.1.3.jar b/j8lib/Jmol-15.1.3.jar
new file mode 100644 (file)
index 0000000..1672e67
Binary files /dev/null and b/j8lib/Jmol-15.1.3.jar differ
index 22c9098..4378457 100644 (file)
@@ -34,6 +34,7 @@ package ext.edu.ucsf.rbvi.strucviz2;
 
 import jalview.bin.Cache;
 import jalview.gui.Preferences;
+import jalview.util.Platform;
 
 import java.io.File;
 import java.io.IOException;
@@ -931,7 +932,7 @@ public class StructureManager
       pathList.add("/usr/local/chimera/bin/chimera");
       pathList.add("/usr/local/bin/chimera");
       pathList.add("/usr/bin/chimera");
-      pathList.add(System.getProperty("user.home") + "/opt/bin/chimera");
+      pathList.add(Platform.getUserPath("opt/bin/chimera"));
     }
     else if (os.startsWith("Windows"))
     {
index b88ef3a..af7db0a 100755 (executable)
@@ -22,6 +22,8 @@ package jalview.analysis;
 
 import jalview.analysis.scoremodels.PIDModel;
 import jalview.analysis.scoremodels.SimilarityParams;
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
@@ -32,6 +34,7 @@ import jalview.datamodel.SequenceNode;
 import jalview.util.QuickSort;
 
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -51,40 +54,64 @@ import java.util.List;
  * from the first tobesorted position in the alignment. e.g. (a,tb2,b,tb1,c,tb3
  * becomes a,tb1,tb2,tb3,b,c)
  */
-public class AlignmentSorter
+public class AlignmentSorter implements ApplicationSingletonI
 {
+
+  private AlignmentSorter()
+  {
+    // private singleton
+  }
+
+  public static AlignmentSorter getInstance()
+  {
+    return (AlignmentSorter) ApplicationSingletonProvider
+            .getInstance(AlignmentSorter.class);
+  }
+
+  /**
+   * types of feature ordering: Sort by score : average score - or total score -
+   * over all features in region Sort by feature label text: (or if null -
+   * feature type text) - numerical or alphabetical Sort by feature density:
+   * based on counts - ignoring individual text or scores for each feature
+   */
+  public static final String FEATURE_SCORE = "average_score";
+
+  public static final String FEATURE_LABEL = "text";
+
+  public static final String FEATURE_DENSITY = "density";
+
   /*
    * todo: refactor searches to follow a basic pattern: (search property, last
    * search state, current sort direction)
    */
-  static boolean sortIdAscending = true;
+  boolean sortIdAscending = true;
 
-  static int lastGroupHash = 0;
+  int lastGroupHash = 0;
 
-  static boolean sortGroupAscending = true;
+  boolean sortGroupAscending = true;
 
-  static AlignmentOrder lastOrder = null;
+  AlignmentOrder lastOrder = null;
 
-  static boolean sortOrderAscending = true;
+  boolean sortOrderAscending = true;
 
-  static TreeModel lastTree = null;
+  TreeModel lastTree = null;
 
-  static boolean sortTreeAscending = true;
+  boolean sortTreeAscending = true;
 
-  /*
+  /**
    * last Annotation Label used for sort by Annotation score
    */
-  private static String lastSortByAnnotation;
+  private String lastSortByAnnotation;
 
-  /*
-   * string hash of last arguments to sortByFeature
-   * (sort order toggles if this is unchanged between sorts)
+  /**
+   * string hash of last arguments to sortByFeature (sort order toggles if this
+   * is unchanged between sorts)
    */
-  private static String sortByFeatureCriteria;
+  private String sortByFeatureCriteria;
 
-  private static boolean sortByFeatureAscending = true;
+  private boolean sortByFeatureAscending = true;
 
-  private static boolean sortLengthAscending;
+  private boolean sortLengthAscending;
 
   /**
    * Sorts sequences in the alignment by Percentage Identity with the given
@@ -115,93 +142,10 @@ public class AlignmentSorter
     }
 
     QuickSort.sort(scores, seqs);
-
     setReverseOrder(align, seqs);
   }
 
   /**
-   * Reverse the order of the sort
-   * 
-   * @param align
-   *          DOCUMENT ME!
-   * @param seqs
-   *          DOCUMENT ME!
-   */
-  private static void setReverseOrder(AlignmentI align, SequenceI[] seqs)
-  {
-    int nSeq = seqs.length;
-
-    int len = 0;
-
-    if ((nSeq % 2) == 0)
-    {
-      len = nSeq / 2;
-    }
-    else
-    {
-      len = (nSeq + 1) / 2;
-    }
-
-    // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
-    List<SequenceI> asq = align.getSequences();
-    synchronized (asq)
-    {
-      for (int i = 0; i < len; i++)
-      {
-        // SequenceI tmp = seqs[i];
-        asq.set(i, seqs[nSeq - i - 1]);
-        asq.set(nSeq - i - 1, seqs[i]);
-      }
-    }
-  }
-
-  /**
-   * Sets the Alignment object with the given sequences
-   * 
-   * @param align
-   *          Alignment object to be updated
-   * @param tmp
-   *          sequences as a vector
-   */
-  private static void setOrder(AlignmentI align, List<SequenceI> tmp)
-  {
-    setOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
-  }
-
-  /**
-   * Sets the Alignment object with the given sequences
-   * 
-   * @param align
-   *          DOCUMENT ME!
-   * @param seqs
-   *          sequences as an array
-   */
-  public static void setOrder(AlignmentI align, SequenceI[] seqs)
-  {
-    // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
-    List<SequenceI> algn = align.getSequences();
-    synchronized (algn)
-    {
-      List<SequenceI> tmp = new ArrayList<>();
-
-      for (int i = 0; i < seqs.length; i++)
-      {
-        if (algn.contains(seqs[i]))
-        {
-          tmp.add(seqs[i]);
-        }
-      }
-
-      algn.clear();
-      // User may have hidden seqs, then clicked undo or redo
-      for (int i = 0; i < tmp.size(); i++)
-      {
-        algn.add(tmp.get(i));
-      }
-    }
-  }
-
-  /**
    * Sorts by ID. Numbers are sorted before letters.
    * 
    * @param align
@@ -221,17 +165,9 @@ public class AlignmentSorter
     }
 
     QuickSort.sort(ids, seqs);
-
-    if (sortIdAscending)
-    {
-      setReverseOrder(align, seqs);
-    }
-    else
-    {
-      setOrder(align, seqs);
-    }
-
-    sortIdAscending = !sortIdAscending;
+    AlignmentSorter as = getInstance();
+    as.sortIdAscending = !as.sortIdAscending;
+    set(align, seqs, as.sortIdAscending);
   }
 
   /**
@@ -254,17 +190,9 @@ public class AlignmentSorter
     }
 
     QuickSort.sort(length, seqs);
-
-    if (sortLengthAscending)
-    {
-      setReverseOrder(align, seqs);
-    }
-    else
-    {
-      setOrder(align, seqs);
-    }
-
-    sortLengthAscending = !sortLengthAscending;
+    AlignmentSorter as = getInstance();
+    as.sortLengthAscending = !as.sortLengthAscending;
+    set(align, seqs, as.sortLengthAscending);
   }
 
   /**
@@ -281,14 +209,16 @@ public class AlignmentSorter
     // ORDERS BY GROUP SIZE
     List<SequenceGroup> groups = new ArrayList<>();
 
-    if (groups.hashCode() != lastGroupHash)
+    AlignmentSorter as = getInstance();
+
+    if (groups.hashCode() != as.lastGroupHash)
     {
-      sortGroupAscending = true;
-      lastGroupHash = groups.hashCode();
+      as.sortGroupAscending = true;
+      as.lastGroupHash = groups.hashCode();
     }
     else
     {
-      sortGroupAscending = !sortGroupAscending;
+      as.sortGroupAscending = !as.sortGroupAscending;
     }
 
     // SORTS GROUPS BY SIZE
@@ -315,7 +245,7 @@ public class AlignmentSorter
 
     // NOW ADD SEQUENCES MAINTAINING ALIGNMENT ORDER
     // /////////////////////////////////////////////
-    List<SequenceI> seqs = new ArrayList<>();
+    List<SequenceI> tmp = new ArrayList<>();
 
     for (int i = 0; i < groups.size(); i++)
     {
@@ -324,68 +254,10 @@ public class AlignmentSorter
 
       for (int j = 0; j < orderedseqs.length; j++)
       {
-        seqs.add(orderedseqs[j]);
-      }
-    }
-
-    if (sortGroupAscending)
-    {
-      setOrder(align, seqs);
-    }
-    else
-    {
-      setReverseOrder(align,
-              vectorSubsetToArray(seqs, align.getSequences()));
-    }
-  }
-
-  /**
-   * Select sequences in order from tmp that is present in mask, and any
-   * remaining sequences in mask not in tmp
-   * 
-   * @param tmp
-   *          thread safe collection of sequences
-   * @param mask
-   *          thread safe collection of sequences
-   * 
-   * @return intersect(tmp,mask)+intersect(complement(tmp),mask)
-   */
-  private static SequenceI[] vectorSubsetToArray(List<SequenceI> tmp,
-          List<SequenceI> mask)
-  {
-    // or?
-    // tmp2 = tmp.retainAll(mask);
-    // return tmp2.addAll(mask.removeAll(tmp2))
-
-    ArrayList<SequenceI> seqs = new ArrayList<>();
-    int i, idx;
-    boolean[] tmask = new boolean[mask.size()];
-
-    for (i = 0; i < mask.size(); i++)
-    {
-      tmask[i] = true;
-    }
-
-    for (i = 0; i < tmp.size(); i++)
-    {
-      SequenceI sq = tmp.get(i);
-      idx = mask.indexOf(sq);
-      if (idx > -1 && tmask[idx])
-      {
-        tmask[idx] = false;
-        seqs.add(sq);
-      }
-    }
-
-    for (i = 0; i < tmask.length; i++)
-    {
-      if (tmask[i])
-      {
-        seqs.add(mask.get(i));
+        tmp.add(orderedseqs[j]);
       }
     }
-
-    return seqs.toArray(new SequenceI[seqs.size()]);
+    set(align, tmp, as.sortGroupAscending);
   }
 
   /**
@@ -401,24 +273,17 @@ public class AlignmentSorter
     // Get an ordered vector of sequences which may also be present in align
     List<SequenceI> tmp = order.getOrder();
 
-    if (lastOrder == order)
-    {
-      sortOrderAscending = !sortOrderAscending;
-    }
-    else
-    {
-      sortOrderAscending = true;
-    }
+    AlignmentSorter as = getInstance();
 
-    if (sortOrderAscending)
+    if (as.lastOrder == order)
     {
-      setOrder(align, tmp);
+      as.sortOrderAscending = !as.sortOrderAscending;
     }
     else
     {
-      setReverseOrder(align,
-              vectorSubsetToArray(tmp, align.getSequences()));
+      as.sortOrderAscending = true;
     }
+    set(align, tmp, as.sortOrderAscending);
   }
 
   /**
@@ -473,26 +338,19 @@ public class AlignmentSorter
   {
     List<SequenceI> tmp = getOrderByTree(align, tree);
 
-    // tmp should properly permute align with tree.
-    if (lastTree != tree)
-    {
-      sortTreeAscending = true;
-      lastTree = tree;
-    }
-    else
-    {
-      sortTreeAscending = !sortTreeAscending;
-    }
+    AlignmentSorter as = getInstance();
 
-    if (sortTreeAscending)
+    // tmp should properly permute align with tree.
+    if (as.lastTree != tree)
     {
-      setOrder(align, tmp);
+      as.sortTreeAscending = true;
+      as.lastTree = tree;
     }
     else
     {
-      setReverseOrder(align,
-              vectorSubsetToArray(tmp, align.getSequences()));
+      as.sortTreeAscending = !as.sortTreeAscending;
     }
+    set(align, tmp, as.sortTreeAscending);
   }
 
   /**
@@ -658,9 +516,12 @@ public class AlignmentSorter
     }
 
     jalview.util.QuickSort.sort(scores, seqs);
-    if (lastSortByAnnotation != scoreLabel)
+
+    AlignmentSorter as = getInstance();
+
+    if (as.lastSortByAnnotation != scoreLabel)
     {
-      lastSortByAnnotation = scoreLabel;
+      as.lastSortByAnnotation = scoreLabel;
       setOrder(alignment, seqs);
     }
     else
@@ -670,18 +531,6 @@ public class AlignmentSorter
   }
 
   /**
-   * types of feature ordering: Sort by score : average score - or total score -
-   * over all features in region Sort by feature label text: (or if null -
-   * feature type text) - numerical or alphabetical Sort by feature density:
-   * based on counts - ignoring individual text or scores for each feature
-   */
-  public static String FEATURE_SCORE = "average_score";
-
-  public static String FEATURE_LABEL = "text";
-
-  public static String FEATURE_DENSITY = "density";
-
-  /**
    * Sort sequences by feature score or density, optionally restricted by
    * feature types, feature groups, or alignment start/end positions.
    * <p>
@@ -708,14 +557,15 @@ public class AlignmentSorter
     if (method != FEATURE_SCORE && method != FEATURE_LABEL
             && method != FEATURE_DENSITY)
     {
-      String msg = String
-              .format("Implementation Error - sortByFeature method must be either '%s' or '%s'",
-                      FEATURE_SCORE, FEATURE_DENSITY);
+      String msg = String.format(
+              "Implementation Error - sortByFeature method must be either '%s' or '%s'",
+              FEATURE_SCORE, FEATURE_DENSITY);
       System.err.println(msg);
       return;
     }
 
-    flipFeatureSortIfUnchanged(method, featureTypes, groups, startCol, endCol);
+    flipFeatureSortIfUnchanged(method, featureTypes, groups, startCol,
+            endCol);
 
     SequenceI[] seqs = alignment.getSequencesArray();
 
@@ -734,8 +584,8 @@ public class AlignmentSorter
        * get sequence residues overlapping column region
        * and features for residue positions and specified types
        */
-      String[] types = featureTypes == null ? null : featureTypes
-              .toArray(new String[featureTypes.size()]);
+      String[] types = featureTypes == null ? null
+              : featureTypes.toArray(new String[featureTypes.size()]);
       List<SequenceFeature> sfs = seqs[i].findFeatures(startCol + 1,
               endCol + 1, types);
 
@@ -809,6 +659,8 @@ public class AlignmentSorter
       }
     }
 
+    boolean doSort = false;
+
     if (FEATURE_SCORE.equals(method))
     {
       if (hasScores == 0)
@@ -834,7 +686,7 @@ public class AlignmentSorter
           }
         }
       }
-      QuickSort.sortByDouble(scores, seqs, sortByFeatureAscending);
+      doSort = true;
     }
     else if (FEATURE_DENSITY.equals(method))
     {
@@ -846,9 +698,13 @@ public class AlignmentSorter
         // System.err.println("Sorting on Density: seq "+seqs[i].getName()+
         // " Feats: "+featureCount+" Score : "+scores[i]);
       }
-      QuickSort.sortByDouble(scores, seqs, sortByFeatureAscending);
+      doSort = true;
+    }
+    if (doSort)
+    {
+      QuickSort.sortByDouble(scores, seqs,
+              getInstance().sortByFeatureAscending);
     }
-
     setOrder(alignment, seqs);
   }
 
@@ -883,16 +739,177 @@ public class AlignmentSorter
     /*
      * if resorting on the same criteria, toggle sort order
      */
-    if (sortByFeatureCriteria == null
-            || !scoreCriteria.equals(sortByFeatureCriteria))
+    AlignmentSorter as = getInstance();
+    if (as.sortByFeatureCriteria == null
+            || !scoreCriteria.equals(as.sortByFeatureCriteria))
     {
-      sortByFeatureAscending = true;
+      as.sortByFeatureAscending = true;
     }
     else
     {
-      sortByFeatureAscending = !sortByFeatureAscending;
+      as.sortByFeatureAscending = !as.sortByFeatureAscending;
     }
-    sortByFeatureCriteria = scoreCriteria;
+    as.sortByFeatureCriteria = scoreCriteria;
+  }
+
+  /**
+   * Set the alignment's sequences list to contain the sequences from a
+   * temporary list, first adding all the elements from the tmp list, then adding all sequences in the alignment that
+   * are not in the list. Option to do the final sort either in order or in reverse order.
+   * 
+   * @param align The alignment being sorted
+   * @param tmp
+   *          the temporary sequence list
+   * @param ascending
+   *          false for reversed order; only sequences already in
+   *          the alignment will be used (which is actually already guaranteed
+   *          by vectorSubsetToArray)
+   */
+  private static void set(AlignmentI align, List<SequenceI> tmp,
+          boolean ascending)
+  {
+    set(align, vectorSubsetToArray(align.getSequences(), tmp), ascending);
+  }
+
+  /**
+   * Set the alignment's sequences list to contain these sequences, either in
+   * this order or its reverse.
+   * 
+   * @param align
+   * @param seqs
+   *          the new sequence array
+   * @param ascending
+   *          false for reversed order; if ascending, only sequences already in
+   *          the alignment will be used; if descending, then a direct 1:1
+   *          replacement is made
+   */
+  private static void set(AlignmentI align, SequenceI[] seqs,
+          boolean ascending)
+  {
+    if (ascending)
+    {
+      setOrder(align, seqs);
+    }
+    else
+    {
+      setReverseOrder(align, seqs);
+    }
+
+  }
+
+  /**
+   * Replace the alignment's sequences with values in an array, clearing the
+   * alignment's sequence list and filtering for sequences that are actually in
+   * the alignment already.
+   * 
+   * @param align
+   *          the Alignment
+   * @param seqs
+   *          the array of replacement values, of any length
+   */
+  public static void setOrder(AlignmentI align, SequenceI[] seqs)
+  {
+    // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
+    List<SequenceI> seqList = align.getSequences();
+    synchronized (seqList)
+    {
+      List<SequenceI> tmp = new ArrayList<>();
+
+      for (int i = 0; i < seqs.length; i++)
+      {
+        if (seqList.contains(seqs[i]))
+        {
+          tmp.add(seqs[i]);
+        }
+      }
+
+      seqList.clear();
+      // User may have hidden seqs, then clicked undo or redo
+      for (int i = 0; i < tmp.size(); i++)
+      {
+        seqList.add(tmp.get(i));
+      }
+    }
+  }
+
+  /**
+   * Replace the alignment's sequences or a subset of those sequences with
+   * values in an array in reverse order. All sequences are replaced; no check
+   * is made that these sequences are in the alignment already.
+   * 
+   * @param align
+   *          the Alignment
+   * @param seqs
+   *          the array of replacement values, length must be less than or equal
+   *          to Alignment.sequences.size()
+   */
+  private static void setReverseOrder(AlignmentI align, SequenceI[] seqs)
+  {
+    int nSeq = seqs.length;
+
+    int len = (nSeq + (nSeq % 2)) / 2;
+    // int len = 0;
+    //
+    // if ((nSeq % 2) == 0)
+    // {
+    // len = nSeq / 2;
+    // }
+    // else
+    // {
+    // len = (nSeq + 1) / 2;
+    // }
+
+    // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
+    List<SequenceI> seqList = align.getSequences();
+    synchronized (seqList)
+    {
+      for (int i = 0; i < len; i++)
+      {
+        // SequenceI tmp = seqs[i];
+        seqList.set(i, seqs[nSeq - i - 1]);
+        seqList.set(nSeq - i - 1, seqs[i]);
+      }
+    }
+  }
+
+  /**
+   * Create and array of reordered sequences in order first from tmp that are
+   * present in seqList already, then, after that, any remaining sequences in
+   * seqList not in tmp. Any sequences in tmp that are not in seqList already
+   * are discarded.
+   * 
+   * @param seqList
+   *          thread safe collection of sequences originally in the alignment
+   * @param tmp
+   *          thread safe collection of sequences or subsequences possibly in
+   *          seqList
+   * 
+   * @return intersect(tmp,seqList)+intersect(complement(tmp),seqList)
+   */
+  private static SequenceI[] vectorSubsetToArray(List<SequenceI> seqList,
+          List<SequenceI> tmp)
+  {
+    ArrayList<SequenceI> seqs = new ArrayList<>();
+    int n = seqList.size();
+    BitSet bs = new BitSet(n);
+    bs.set(0, n);
+    for (int i = 0, nt = tmp.size(); i < nt; i++)
+    {
+      SequenceI sq = tmp.get(i);
+      int idx = seqList.indexOf(sq);
+      if (idx >= 0 && bs.get(idx))
+      {
+        seqs.add(sq);
+        bs.clear(idx);
+      }
+    }
+
+    for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
+    {
+      seqs.add(seqList.get(i));
+    }
+
+    return seqs.toArray(new SequenceI[seqs.size()]);
   }
 
 }
index c54357e..b70e9f7 100644 (file)
@@ -31,6 +31,7 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.util.DBRefUtils;
 import jalview.util.MapList;
+import jalview.ws.SequenceFetcher;
 import jalview.ws.SequenceFetcherFactory;
 import jalview.ws.seqfetcher.ASequenceFetcher;
 
@@ -402,7 +403,6 @@ public class CrossRef
   private void retrieveCrossRef(List<DBRefEntry> sourceRefs, SequenceI seq,
           List<DBRefEntry> xrfs, boolean fromDna, AlignedCodonFrame cf)
   {
-    ASequenceFetcher sftch = SequenceFetcherFactory.getSequenceFetcher();
     SequenceI[] retrieved = null;
     SequenceI dss = seq.getDatasetSequence() == null ? seq
             : seq.getDatasetSequence();
@@ -418,7 +418,7 @@ public class CrossRef
     }
     try
     {
-      retrieved = sftch.getSequences(sourceRefs, !fromDna);
+      retrieved = SequenceFetcher.getInstance().getSequences(sourceRefs, !fromDna);
     } catch (Exception e)
     {
       System.err.println(
index ebc9a26..8700ec0 100644 (file)
@@ -22,6 +22,8 @@ package jalview.analysis.scoremodels;
 
 import jalview.api.AlignmentViewPanel;
 import jalview.api.analysis.ScoreModelI;
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.io.DataSourceType;
 import jalview.io.FileParse;
 import jalview.io.ScoreMatrixFile;
@@ -33,18 +35,8 @@ import java.util.Map;
 /**
  * A class that can register and serve instances of ScoreModelI
  */
-public class ScoreModels
+public class ScoreModels implements ApplicationSingletonI
 {
-  private final ScoreMatrix BLOSUM62;
-
-  private final ScoreMatrix PAM250;
-
-  private final ScoreMatrix DNA;
-
-  private static ScoreModels instance;
-
-  private Map<String, ScoreModelI> models;
-
   /**
    * Answers the singleton instance of this class, with lazy initialisation
    * (built-in score models are loaded on the first call to this method)
@@ -53,11 +45,7 @@ public class ScoreModels
    */
   public static ScoreModels getInstance()
   {
-    if (instance == null)
-    {
-      instance = new ScoreModels();
-    }
-    return instance;
+    return (ScoreModels) ApplicationSingletonProvider.getInstance(ScoreModels.class);
   }
 
   /**
@@ -84,6 +72,14 @@ public class ScoreModels
     registerScoreModel(new FeatureDistanceModel());
   }
 
+  private final ScoreMatrix BLOSUM62;
+
+  private final ScoreMatrix PAM250;
+
+  private final ScoreMatrix DNA;
+
+  private Map<String, ScoreModelI> models;
+
   /**
    * Tries to load a score matrix from the given resource file, and if
    * successful, registers it.
@@ -153,7 +149,7 @@ public class ScoreModels
    */
   public void reset()
   {
-    instance = new ScoreModels();
+    ApplicationSingletonProvider.removeInstance(this.getClass());
   }
 
   /**
diff --git a/src/jalview/api/JalviewApp.java b/src/jalview/api/JalviewApp.java
deleted file mode 100644 (file)
index e488d6e..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package jalview.api;
-
-import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.PDBEntry;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.io.DataSourceType;
-import jalview.io.NewickFile;
-import jalview.javascript.JSFunctionExec;
-import jalview.javascript.MouseOverStructureListener;
-import jalview.structure.SelectionSource;
-import jalview.structure.VamsasSource;
-
-//import java.applet.AppletContext;
-import java.io.IOException;
-import java.net.URL;
-import java.util.Hashtable;
-import java.util.Vector;
-
-import netscape.javascript.JSObject;
-
-public interface JalviewApp
-{
-  public String getParameter(String name);
-
-  public boolean getDefaultParameter(String name, boolean def);
-
-  public URL getDocumentBase();
-
-  public URL getCodeBase();
-
-  public void setAlignPdbStructures(boolean defaultParameter);
-
-  public void newStructureView(PDBEntry pdb, SequenceI[] seqs,
-          String[] chains, DataSourceType protocol);
-
-  public void alignedStructureView(PDBEntry[] pdb, SequenceI[][] seqs,
-          String[][] chains, String[] protocols);
-
-  public void updateForAnnotations();
-
-  public AlignViewportI getViewport();
-
-  public void setFeatureGroupState(String[] groups, boolean state);
-
-  public boolean parseFeaturesFile(String param, DataSourceType protocol);
-
-  public void newFeatureSettings();
-
-  public boolean loadScoreFile(String sScoreFile) throws IOException;
-
-  public void loadTree(NewickFile fin, String treeFile) throws IOException;
-
-  public Vector<Runnable> getJsExecQueue(JSFunctionExec jsFunctionExec);
-
-// deprecated  public AppletContext getAppletContext();
-
-  public boolean isJsfallbackEnabled();
-
-  public JSObject getJSObject();
-
-  public StructureSelectionManagerProvider getStructureSelectionManagerProvider();
-
-  public void updateColoursFromMouseOver(Object source,
-          MouseOverStructureListener mouseOverStructureListener);
-
-  public Object[] getSelectionForListener(SequenceGroup seqsel, ColumnSelection colsel,
-          HiddenColumns hidden, SelectionSource source, Object alignFrame);
-
-  public String arrayToSeparatorList(String[] array);
-
-  public Hashtable<String, int[]> getJSHashes();
-
-  Hashtable<String, Hashtable<String, String[]>> getJSMessages();
-
-  public Object getFrameForSource(VamsasSource source);
-
-  public jalview.renderer.seqfeatures.FeatureRenderer getNewFeatureRenderer(
-          AlignViewportI vp);
-
-}
diff --git a/src/jalview/api/JalviewJSApi.java b/src/jalview/api/JalviewJSApi.java
new file mode 100644 (file)
index 0000000..895fd15
--- /dev/null
@@ -0,0 +1,412 @@
+package jalview.api;
+
+import java.net.URL;
+
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+
+/**
+ * JAL-3369 JalviewJS API BH 2019.07.17
+ * 
+ * @author hansonr
+ *
+ */
+public interface JalviewJSApi
+{
+
+  /**
+   * bind a pdb file to a sequence in the given AlignFrame.
+   * 
+   * @param sequenceId
+   *          - sequenceId within the dataset or null
+   * @param pdbId
+   *          - the four-character PDB ID
+   * @param pdbFile
+   *          - pdb file - either a URL or a valid PDB file or null.
+   * @param alFrame
+   *          - null or specific AlignFrame. This specifies the dataset that
+   *          will be searched for a seuqence called sequenceId
+   * 
+   * @return true if binding was success
+   */
+  public boolean addPdbFile(String sequenceId, String pdbId, String pdbFile,
+          AlignFrame alFrame);
+
+  /**
+   * Get alignment as format with or without the jalview start-end sequence
+   * suffix appended.
+   * 
+   * @param format
+   * @param addSuffix
+   *          (default false)
+   * @param alf
+   *          (default current)
+   * 
+   * @return
+   */
+  public String getAlignment(String format, boolean addSuffix,
+          AlignFrame alf);
+
+  /**
+   * Get an array of sequence IDs reflecting the order of the alignment in the
+   * specified alignment frame
+   * 
+   * @param alf
+   *          (default current)
+   * @return array of sequence IDs
+   */
+  public String[] getAlignmentOrder(AlignFrame alf);
+
+  /**
+   * Get alignment view alf's annotation as an annotation file
+   * 
+   * @param alf
+   *          (default current)
+   * @return annotation
+   */
+  public String getAnnotation(AlignFrame alf);
+
+  /**
+   * Get the URL for the location where the code is found; typically ending in
+   * "swingjs/j2s".
+   * 
+   * @return web page URL
+   */
+  public URL getCodeBase();
+
+  AlignFrame getCurrentAlignFrame();
+
+  /**
+   * Get the URL for the hosting web page.
+   * 
+   * @return web page URL
+   */
+  public URL getDocumentBase();
+
+  /**
+   * Get the array of feature groups for an alignment frame.
+   * 
+   * @param alf
+   *          AlignFrame to get feature groups for (default current)
+   * @return
+   */
+  public String[] getFeatureGroups(AlignFrame alf);
+
+  /**
+   * Get the array of feature groups for an alignment frame with a specific
+   * on/off state.
+   * 
+   * @param visible
+   *          (default off)
+   * @param alf
+   *          align frame (default current)
+   * 
+   * @return
+   */
+  public String[] getFeatureGroupsOfState(boolean visible, AlignFrame alf);
+
+  /**
+   * Get the sequence features in the alignment frame in the given format
+   * (Jalview or GFF). Two additional options can be added to the format, each
+   * starting with a semicolon:
+   * 
+   * ;includeNonpositional (default) or ;positionalOnly
+   * 
+   * ;includeComplement
+   * 
+   * @param format
+   *          case-insensitive "Jalview" or "GFF" (default "GFF")
+   * @param alf
+   *          (default current)
+   * @return formatted sequence features
+   */
+  public String getFeatures(String format, AlignFrame alf);
+
+  /**
+   * Get an applet parameter as a string.
+   * 
+   * @param name
+   * @return value or null
+   */
+  public String getParameter(String name);
+
+  /**
+   * Get an applet parameter object value.
+   * 
+   * @param name
+   * @return value or null
+   */
+  public Object getParameterAsObject(String name);
+
+  /**
+   * @param alf
+   *          AlignFrame containing selection
+   * @return String list of selected sequence IDs, each terminated by current
+   *         default separator sequence
+   * 
+   */
+  public SequenceI[] getSelectedSequences(AlignFrame alf);
+
+  // BH incompatibility here -- JalviewLite created an AlignFrame; Jalview
+  // creates an AlignmentPanel
+  // /**
+  // * create a new view and return the AlignFrame instance
+  // *
+  // * @return
+  // */
+  //
+  // public AlignFrame newView();
+  //
+  // /**
+  // * create a new view named name and return the AlignFrame instance
+  // *
+  // * @param name
+  // * @return
+  // */
+  //
+  // public AlignFrame newView(String name);
+  //
+  // /**
+  // * create a new view on alf and return the AlignFrame instance
+  // *
+  // * @param alf
+  // * @return
+  // */
+  // public AlignFrame newViewFrom(AlignFrame alf);
+  //
+  // /**
+  // * create a new view named name on alf
+  // *
+  // * @param alf
+  // * @param name
+  // * @return
+  // */
+  // public AlignFrame newViewFrom(AlignFrame alf, String name);
+  //
+
+  /**
+   * get sequences selected in alf and return their alignment in format 'format'
+   * either with or without suffix
+   * 
+   * @param format
+   *          - format of alignment file
+   * @param alf
+   *          - where selection is
+   * @param suffix
+   *          - true to append /start-end string to each sequence ID
+   * 
+   * @return selected sequences as flat file or empty string if there was no
+   *         current selection
+   */
+  public String getSelectedSequencesAsAlignment(String format,
+          boolean addSuffix, AlignFrame alf);
+
+  /**
+   * 
+   * @param sequenceId
+   *          id of sequence to highlight
+   * @param position
+   *          integer position [ tobe implemented or range ] on sequence
+   * @param alignedPosition
+   *          false, blank or something else - indicate if position is an
+   *          alignment column or unaligned sequence position
+   * @param alf
+   *          alignment frame (default current)
+   */
+  public void highlight(String sequenceId, String position,
+          String alignedPosition, AlignFrame alf);
+
+  /**
+   * 
+   * @param data
+   *          alignment data as a string
+   * @param title
+   *          window title
+   * @param width
+   *          desired width or 0 for default width
+   * @param height
+   *          desired height or 0 for default height
+   * @return null or new alignment frame
+   */
+
+  public AlignFrame loadAlignment(String data, String title, int width,
+          int height);
+
+  /**
+   * add the given features or annotation to the given alignment view
+   * 
+   * @param annotation
+   * @param alf
+   *          alignment frame (default current)
+   */
+  public void loadAnnotation(String annotation, AlignFrame alf);
+
+  /**
+   * Parse the given string as a jalview feature or GFF annotation file and
+   * optionally enable feature display on the given AlignFrame.
+   * 
+   * @param features
+   *          - gff or features file
+   * @param autoenabledisplay
+   *          - when true, feature display will be enabled if any features can
+   *          be parsed from the string.
+   * @param alf
+   *          alignment frame (default current)
+   * 
+   * @return true if data parsed as features
+   */
+  public boolean loadFeatures(String features, boolean autoenabledisplay,
+          AlignFrame alf);
+
+  /**
+   * Load a score file.
+   * 
+   * @param sScoreFile
+   * @param alf
+   *          alignment frame (default current)
+   * 
+   * @return
+   */
+  public boolean loadScoreFile(String sScoreFile, AlignFrame alf);
+
+  /**
+   * public static method for JalviewJS API to open a PCAPanel without
+   * necessarily using a dialog.
+   * 
+   * @param modelName
+   * @param alf
+   *          may be null
+   * 
+   * @return the PCAPanel, or the string "label.you_need_at_least_n_sequences"
+   *         if number of sequences selected is inappropriate
+   */
+  public Object openPcaPanel(String modelName, AlignFrame alf);
+
+  /**
+   * Open a new Tree panel on the desktop statically. Params are standard (not
+   * set by Groovy). No dialog is opened.
+   * 
+   * @param treeType
+   * @param modelName
+   * @param alf
+   *          align frame (default current)
+   * 
+   * @return null, or the string "label.you_need_at_least_n_sequences" if number
+   *         of sequences selected is inappropriate
+   */
+  public Object openTreePanel(String treeType, String modelName,
+          AlignFrame alf);
+
+  /**
+   * re-order the given alignment using the given array of sequence IDs
+   * 
+   * @param ids
+   *          array of sequence IDs
+   * @param undoName
+   *          - string to use when referring to ordering action in undo buffer
+   * @param alf
+   *          alignment frame (default current)
+   * 
+   * @return 'true' if alignment was actually reordered. empty string if
+   *         alignment did not contain sequences.
+   */
+  public boolean orderAlignment(String[] ids, String undoName,
+          AlignFrame alf);
+
+  /**
+   * process commandline arguments after the JavaScript application has started
+   *
+   * @param args
+   * @return
+   */
+  public Object parseArguments(String[] args);
+
+  boolean parseFeaturesFile(String filename, AlignFrame alf);
+
+  // Bob's additions:
+
+  /**
+   * remove any callback using the given listener function and associated with
+   * the given AlignFrame (or null for all callbacks);
+   * 
+   * @param listener
+   *          (may be null);
+   * @param alf
+   *          alignment frame (default current)
+   */
+  public void removeSelectionListener(String listener, AlignFrame af);
+
+  /**
+   * adjust horizontal/vertical scroll to make the given location the top left
+   * hand corner for the given view
+   * 
+   * @param topRow
+   *          -1 for current top row
+   * @param leftHandColumn
+   *          -1 for current left-hand column
+   * @param alf
+   *          alignment frame (default current)
+   */
+  public void scrollViewTo(int topRow, int leftHandColumn, AlignFrame alf);
+  /**
+   * select regions of the given alignment frame
+   * 
+   * @param alf
+   *          alignment frame (default current)
+   * @param toselect
+   *          String separated list { column range, seq1...seqn sequence ids }
+   * @param sep
+   *          separator between toselect fields
+   */
+  public void select(String[] sequenceIds, String[] columns,
+          AlignFrame alf);
+  
+  /**
+   * Set the state (visible or not) of the selected feature groups.
+   * 
+   * @param groups
+   * @param state
+   * @param alf
+   *          (default current)
+   */
+  public void setFeatureGroupState(String[] groups, boolean state,
+          AlignFrame alf);
+
+  /**
+   * Register a JavaScript function to handle alignment selection events. Events
+   * are generated when the user completes a selection event, or when the user
+   * deselects all selected regions. The method is called back with the
+   * following arguments:
+   * 
+   * the appletID (such as "Jalview1")
+   * 
+   * the source alignment frame
+   * 
+   * the SelectionSource object (for example, an AlignViewport)
+   * 
+   * the sequence set ID
+   * 
+   * an array of sequence IDs
+   * 
+   * an array of columns (single number or hyphenated range)
+   * 
+   * @param listener
+   *          name of JavaScript function to be called
+   * 
+   * @param alf
+   *          alignment frame (default ALL)
+   */
+  public void setSelectionListener(String listener, AlignFrame alf);
+
+  public void showOverview();
+
+  /**
+   * 
+   * @param pdbID
+   * @param fileType
+   * @param alf
+   *          align frame (default current)
+   */
+  public void showStructure(String pdbID, String fileType, AlignFrame alf);
+
+}
index e1bfbde..311f1e7 100644 (file)
@@ -773,8 +773,7 @@ public class APopupMenu extends java.awt.PopupMenu
 
           ap.alignFrame.addHistoryItem(editCommand);
 
-          ap.av.firePropertyChange("alignment", null,
-                  ap.av.getAlignment().getSequences());
+          ap.av.notifyAlignment();
         }
       }
     }
@@ -811,8 +810,7 @@ public class APopupMenu extends java.awt.PopupMenu
 
         ap.alignFrame.addHistoryItem(caseCommand);
 
-        ap.av.firePropertyChange("alignment", null,
-                ap.av.getAlignment().getSequences());
+        ap.av.notifyAlignment();
 
       }
     }
index 1a46585..fc1f26d 100644 (file)
@@ -882,7 +882,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     }
     else if (source == autoCalculate)
     {
-      viewport.autoCalculateConsensus = autoCalculate.getState();
+      viewport.setAutoCalculateConsensusAndConservation(autoCalculate.getState());
     }
     else if (source == sortByTree)
     {
@@ -1701,8 +1701,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
                                           // viewport.getColumnSelection().getHiddenColumns()
                                           // != null;
     updateEditMenuBar();
-    originalSource.firePropertyChange("alignment", null,
-            originalSource.getAlignment().getSequences());
+    originalSource.notifyAlignment();
   }
 
   /**
@@ -1734,8 +1733,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
                                           // != null;
 
     updateEditMenuBar();
-    originalSource.firePropertyChange("alignment", null,
-            originalSource.getAlignment().getSequences());
+    originalSource.notifyAlignment();
   }
 
   AlignmentViewport getOriginatingSource(CommandI command)
@@ -2084,8 +2082,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     viewport.getRanges().setEndSeq(viewport.getAlignment().getHeight() - 1); // BH
                                                                              // 2019.04.18
     viewport.getAlignment().getWidth();
-    viewport.firePropertyChange("alignment", null,
-            viewport.getAlignment().getSequences());
+    viewport.notifyAlignment();
 
   }
 
@@ -2159,8 +2156,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     viewport.setSelectionGroup(null);
     viewport.getAlignment().deleteGroup(sg);
 
-    viewport.firePropertyChange("alignment", null,
-            viewport.getAlignment().getSequences());
+    viewport.notifyAlignment();
 
     if (viewport.getAlignment().getHeight() < 1)
     {
@@ -2372,7 +2368,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
         }
       }
 
-      viewport.firePropertyChange("alignment", null, al.getSequences());
+      viewport.notifyAlignment();
     }
   }
 
@@ -2416,7 +2412,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     // if (viewport.hasHiddenColumns)
     // viewport.getColumnSelection().compensateForEdits(shifts);
     ranges.setStartRes(seq.findIndex(startRes) - 1);
-    viewport.firePropertyChange("alignment", null, al.getSequences());
+    viewport.notifyAlignment();
 
   }
 
@@ -2450,7 +2446,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
     ranges.setStartRes(seq.findIndex(startRes) - 1);
 
-    viewport.firePropertyChange("alignment", null, al.getSequences());
+    viewport.notifyAlignment();
 
   }
 
index 0324d8c..b379c2d 100644 (file)
@@ -352,7 +352,7 @@ public class AlignViewport extends AlignmentViewport
     if (mappedCommand != null)
     {
       mappedCommand.doCommand(null);
-      firePropertyChange("alignment", null, getAlignment().getSequences());
+      notifyAlignment();
 
       // ap.scalePanelHolder.repaint();
       // ap.repaint();
index bd36b0d..6488c61 100644 (file)
@@ -227,8 +227,7 @@ public class RedundancyPanel extends SliderPanel
       ap.alignFrame.addHistoryItem(cut);
 
       PaintRefresher.Refresh(this, ap.av.getSequenceSetId(), true, true);
-      ap.av.firePropertyChange("alignment", null,
-              ap.av.getAlignment().getSequences());
+      ap.av.notifyAlignment();
     }
 
   }
@@ -243,8 +242,7 @@ public class RedundancyPanel extends SliderPanel
     {
       ap.av.getHistoryList().remove(command);
       ap.alignFrame.updateEditMenuBar();
-      ap.av.firePropertyChange("alignment", null,
-              ap.av.getAlignment().getSequences());
+      ap.av.notifyAlignment();
     }
 
     ap.paintAlignment(true, true);
index 776e9ad..a1e2340 100644 (file)
@@ -132,8 +132,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     if (editCommand != null && editCommand.getSize() > 0)
     {
       ap.alignFrame.addHistoryItem(editCommand);
-      av.firePropertyChange("alignment", null,
-              av.getAlignment().getSequences());
+      av.notifyAlignment();
     }
 
     startseq = -1;
index cb26fb5..46d6c11 100755 (executable)
@@ -715,8 +715,10 @@ public class TreeCanvas extends Panel
     ap.updateAnnotation();
     if (av.getCodingComplement() != null)
     {
-      ((AlignmentViewport) av.getCodingComplement()).firePropertyChange(
-              "alignment", null, ap.av.getAlignment().getSequences());
+      ((AlignmentViewport) av.getCodingComplement()).notifyAlignment();
+      // Technically, the property change is not the same because av is not necessarily getCodingComplement(), 
+      // but this is the appletgui, so I am not going to worry about it. BH
+      //.firePropertyChange("alignment", null, ap.av.getAlignment().getSequences());
     }
   }
 
similarity index 99%
rename from src/jalview/javascript/JSFunctionExec.java
rename to src/jalview/appletgui/js/JSFunctionExec.java
index 29f3fa9..247552b 100644 (file)
@@ -18,7 +18,7 @@
  * 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.bin.JalviewLite;
 
@@ -18,7 +18,7 @@
  * 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.appletgui.AlignFrame;
 
@@ -455,7 +455,7 @@ public interface JalviewLiteJsApi
    *          - separator separated list of PDB file URIs that this viewer is
    *          handling. These files must be in the same order they appear in
    *          Jmol (e.g. first one is frame 1, second is frame 2, etc).
-   * @see jalview.javascript.MouseOverStructureListener
+   * @see jalview.appletgui.js.MouseOverStructureListener
    */
   public abstract void setStructureListener(String listener,
           String modelSet);
similarity index 97%
rename from src/jalview/javascript/JsCallBack.java
rename to src/jalview/appletgui/js/JsCallBack.java
index 76d6e8d..2fe4dac 100644 (file)
@@ -18,7 +18,7 @@
  * 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;
 
 public interface JsCallBack
 {
@@ -18,7 +18,7 @@
  * 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.appletgui.AlignFrame;
 import jalview.bin.JalviewLite;
@@ -18,7 +18,7 @@
  * 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.appletgui.AlignFrame;
 import jalview.bin.JalviewLite;
  * 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;
index 6a23c39..726734d 100644 (file)
@@ -1,23 +1,33 @@
-package jalview.bin;
 
-import jalview.gui.Preferences;
+package jalview.bin;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
-import java.util.Vector;
+
+import jalview.gui.Preferences;
 
 /**
- * Collection of all known applet tags from JalviewLite
+ * Collection of all known applet tags from JalviewLite.
+ * Three cases; can be one or more of these:
+ * 
+ * CASE I. args[] name and value for ArgsParser
+ * 
+ * CASE II. applet parameter for JalviewJSApp
+ * 
+ * CASE III. mapped to a Preference
+ * 
  * 
  * @author hansonr
  *
  */
 @SuppressWarnings("serial")
-public class AppletParams extends HashMap<String, String>
+public class AppletParams extends HashMap<String, Object>
 {
 
-  private final static String[] params = { "alignpdbfiles",
-      Preferences.ANNOTATIONCOLOUR_MAX, Preferences.ANNOTATIONCOLOUR_MIN,
+  private final static String[] params = { 
+      "alignpdbfiles",
+      "ANNOTATIONCOLOUR_MAX", "ANNOTATIONCOLOUR_MIN",
       "annotations",
       "APPLICATION_URL", "automaticScrolling", "centrecolumnlabels",
       "debug", "defaultColour", "defaultColourNuc", "defaultColourProt",
@@ -37,18 +47,10 @@ public class AppletParams extends HashMap<String, String>
       "userDefinedColour", "widthScale", "windowHeight", "windowWidth",
       "wrap", };
 
-  public AppletParams(Map<String, String> info)
-  {
-    for (int i = params.length; --i >= 0;)
-    {
-      put(params[i], info.get(params[i]));
-    }
-  }
-
   public String getParam(String param, String def)
   {
-    String val = get(param);
-    return (val != null ? val : def);
+    Object val = get(param);
+    return (val != null ? val.toString() : def);
   }
 
   // <applet
@@ -103,14 +105,40 @@ public class AppletParams extends HashMap<String, String>
 
   public AppletParams()
   {
-    // TODO Auto-generated constructor stub
+  }
+
+  public static AppletParams getAppletParams(Map<String, Object> map,
+          List<String> vargs)
+  {
+    AppletParams appletParams = new AppletParams();
+    String resourcePath = getString(map, "resourcePath");
+    if (resourcePath == null)
+      resourcePath = "";
+    if (resourcePath.length() > 0 && !resourcePath.endsWith("/"))
+    {
+      resourcePath += "/";
+    }
+    for (int i = params.length; --i >= 0;)
+    {
+      String prefName = params[i];
+      Object value = map.get(prefName);
+      if (value != null)
+        addParam(vargs, prefName, value, appletParams, resourcePath);
+    }
+    return appletParams;
+  }
+
+  private static String getString(Map<String, Object> map, String key)
+  {
+    Object o = map.get(key);  
+    return (o == null ? null : o.toString());
   }
 
   public static AppletParams getAppletParams(String[] args,
-          Vector<String> vargs)
+          List<String> vargs)
   {
     AppletParams appletParams = new AppletParams();
-    String resourcePath = null;
+    String resourcePath = "";
     for (int i = args.length; --i > 0;) // > 0 is correct, not >=0
     {
       if (args[i].startsWith("name=\"Info.resourcePath\""))
@@ -129,265 +157,269 @@ public class AppletParams extends HashMap<String, String>
       if (arg.startsWith("name="))
       {
         String prefName = getAttr(arg, "name");
-        String appletName = prefName.toLowerCase();
-        String argName = prefName;
         String value = getAttr(arg, "value");
+        addParam(vargs, prefName, value, appletParams, resourcePath);
+      }
+    }
+    return appletParams;
+  }
 
-        // note that Application arguments ARE case-sensitive, but
-        // Applet.getParameter() is not.
+  private static void addParam(List<String> vargs, String prefName,
+          Object value, AppletParams appletParams, String resourcePath)
+  {
 
-        switch (appletName)
-        {
+    // note that Application arguments ARE case-sensitive, but
+    // Applet.getParameter() is not.
 
-        case "file":
-          argName = "open";
-          appletName = null;
-          value = resourcePath + value;
-          break;
-        case "file2":
-          argName = "open2";
-          prefName = null;
-          value = resourcePath + value;
-          break;
-        case "features":
-        case "jnetfile":
-        case "jpredfile":
-        case "pdbfile":
-        case "scorefile":
-        case "sequence":
-          // setting argName to null indicates that we want
-          // JalviewAppLoader to take care of this.
-          prefName = argName = null;
-          value = resourcePath + value;
-          break;
-        case "tree":
-        case "treefile":
-          // setting appletName to null indicates that we want
-          // Jalview.doMain to taken care of this as Jalview args
-          argName = "tree";
-          appletName = null;
-          value = resourcePath + value;
-          break;
-
-        // non-loading preferences
-
-        case "defaultcolour":
-          prefName = Preferences.DEFAULT_COLOUR;
-          break;
-        case "defaultcolournuc":
-          prefName = Preferences.DEFAULT_COLOUR_NUC;
-          break;
-        case "defaultcolourprot":
-          prefName = Preferences.DEFAULT_COLOUR_PROT;
-          break;
-        case "annotationcolour_max":
-          prefName = Preferences.ANNOTATIONCOLOUR_MAX;
-          break;
-        case "annotationcolour_min":
-          prefName = Preferences.ANNOTATIONCOLOUR_MIN;
-          break;
-        case "enablesplitframe":
-          prefName = Preferences.ENABLE_SPLIT_FRAME;
-          break;
-        case "centrecolumnlabels":
-          prefName = Preferences.CENTRE_COLUMN_LABELS;
-          break;
-        case "sortby":
-          prefName = Preferences.SORT_ALIGNMENT; // id, etc.
-          break;
-        case "normalisesequencelogo":
-          prefName = Preferences.NORMALISE_CONSENSUS_LOGO;
-          break;
-        case "relaxedidmatch":
-          prefName = Preferences.RELAXEDSEQIDMATCHING;
-          break;
-        case "scaleproteinascdna":
-          prefName = Preferences.SCALE_PROTEIN_TO_CDNA;
-          break;
-        case "userdefinedcolour":
-          argName = "colour";
-          prefName = Preferences.USER_DEFINED_COLOURS;
-          break;
-        case "wrap":
-          prefName = Preferences.WRAP_ALIGNMENT;
-          break;
-
-        // implemented; not tested:
-
-        case "oninit":
-          prefName = null;
-          break;
-        case "annotations":
-          value = resourcePath + value;
-          argName = null;
-          break;
-        case "hidefeaturegroups":
-          // TODO
-          break;
-        case "pdbseq":
-          argName = prefName = null;
-          break;
-        case "sortbytree":
-          prefName = Preferences.SORT_BY_TREE;
-          value = checkTF(value);
-          appletName = null; // taken care of by Jalview
-          break;
-        case "format":
-          break;
-        case "alignpdbfiles":
-          argName = prefName = null;
-          break;
-        case "separator":
-          break;
-
-        // TODO: probably not relevant?
-
-        case "rgb":
-          prefName = null; // TODO no background for application?
-          break;
-        case "externalstructureviewer":
-          break;
-        case "application_url":
-          break;
-        case "automaticscrolling":
-          break;
-        case "heightscale":
-          break;
-        case "jalviewhelpurl":
-          break;
-        case "label":
-          break;
-        case "linklabel_":
-          prefName = "linkLabel_";
-          break;
-        case "linklabel_1":
-          prefName = "linkLabel_1";
-          break;
-        case "linkurl_":
-          prefName = "linkURL_";
-          break;
-
-        // unknown:
-
-        case "nojmol":
-        case "normaliselogo":
-        case "resolvetocodebase":
-        case "uppercase":
-        case "widthscale":
-        case "windowheight":
-        case "windowwidth":
-          argName = prefName = null;
-          break;
-
-        // TRUE/FALSE
-
-        case "debug":
-          value = checkTF(value);
-          break;
-        case "embedded":
-          value = checkTF(value);
-          break;
-        case "showbutton":
-          value = checkTF(value);
-          break;
-        case "showannotation":
-          prefName = Preferences.SHOW_ANNOTATIONS;
-          value = checkTF(value);
-          break;
-        case "showconsensus":
-          prefName = Preferences.SHOW_CONSENSUS_LOGO;
-          value = checkTF(value);
-          break;
-        case "showconsensushistogram":
-          prefName = Preferences.SHOW_CONSENSUS_HISTOGRAM;
-          value = checkTF(value);
-          break;
-        case "showconservation":
-          prefName = Preferences.SHOW_CONSERVATION;
-          value = checkTF(value);
-          break;
-        case "showgroupconsensus":
-          prefName = Preferences.SHOW_GROUP_CONSENSUS;
-          value = checkTF(value);
-          break;
-        case "showgroupconservation":
-          prefName = Preferences.SHOW_GROUP_CONSERVATION;
-          value = checkTF(value);
-          break;
-        case "showoccupancy":
-          prefName = Preferences.SHOW_OCCUPANCY;
-          value = checkTF(value);
-          break;
-        case "showquality":
-          prefName = Preferences.SHOW_QUALITY;
-          value = checkTF(value);
-          break;
-        case "showsequencelogo":
-          prefName = Preferences.SHOW_CONSENSUS_LOGO;
-          value = checkTF(value);
-          break;
-        case "showfeaturegroups":
-          value = checkTF(value);
-          break;
-        case "showfeaturesettings":
-          value = checkTF(value);
-          break;
-        case "showfullid":
-          value = checkTF(value);
-          break;
-        case "showtreebootstraps":
-          value = checkTF(value);
-          break;
-        case "showtreedistances":
-          value = checkTF(value);
-          break;
-        case "showunconserved":
-          prefName = Preferences.SHOW_UNCONSERVED;
-          value = checkTF(value);
-          break;
-        case "showunlinkedtreenodes":
-          value = checkTF(value);
-          break;
-        default:
-          if (appletName.startsWith("pdbfile")
-                  || appletName.startsWith("sequence") && Character.isDigit(
-                          appletName.charAt(appletName.length() - 1)))
-          {
-            // could be pdbFile2, for example
-            prefName = argName = null;
-            value = resourcePath + value;
-            break;
-          }
-          // or one of the app preference names
-          break;
-        }
-        // put name and value into application args
-        if (value != null && argName != null)
-        {
-          vargs.add(argName);
-          if (value != "true")
-          {
-            vargs.add(value);
-          }
-        }
-        if (value == null)
-        {
-          value = "false";
-        }
-        System.out.println("AppletParams propName=" + prefName + " argName="
-                + argName + " appletName="
-                + appletName + " value=" + value);
-        if (appletName != null)
-        {
-          appletParams.put(appletName, value);
-        }
-        if (prefName != null)
-        {
-          Cache.setPropertyNoSave(prefName, value);
-        }
+    // prefName // CASE III
+
+    String argName = null;  // CASE I                
+
+    String appletName = prefName.toLowerCase();   // CASE II
+
+    // by nulling one or more of these names, that route will not be used.
+    
+    switch (appletName)
+    {
+
+    case "file":
+      argName = "open";
+      prefName = null;
+      value = resourcePath + value;
+      break;
+    case "file2":
+      argName = "open2";
+      prefName = null;
+      value = resourcePath + value;
+      break;
+    case "oninit":
+    case "hidefeaturegroups":
+      // applet parameter only
+      // setting argName to null indicates that we want
+      // JalviewJSApp to take care of this using getParameter or getParameterAsObject      
+     prefName = argName = null;
+      break;
+    case "tree":
+    case "treefile":
+      // setting appletName to null indicates that we want
+      // Jalview.doMain to taken care of this as Jalview args
+      argName = "tree";
+      prefName = null;
+      value = resourcePath + value;
+      break;
+
+    case "features":
+    case "jnetfile":
+    case "jpredfile":
+    case "pdbfile":
+    case "scorefile":
+    case "sequence":
+    case "annotations":
+      prefName = argName = null;
+      value = resourcePath + value;
+      break;
+
+      // non-loading preferences
+
+    case "defaultcolour":
+      prefName = Preferences.DEFAULT_COLOUR;
+      break;
+    case "defaultcolournuc":
+      prefName = Preferences.DEFAULT_COLOUR_NUC;
+      break;
+    case "defaultcolourprot":
+      prefName = Preferences.DEFAULT_COLOUR_PROT;
+      break;
+    case "annotationcolour_max":
+      prefName = Preferences.ANNOTATIONCOLOUR_MAX;
+      break;
+    case "annotationcolour_min":
+      prefName = Preferences.ANNOTATIONCOLOUR_MIN;
+      break;
+    case "enablesplitframe":
+      prefName = Preferences.ENABLE_SPLIT_FRAME;
+      break;
+    case "centrecolumnlabels":
+      prefName = Preferences.CENTRE_COLUMN_LABELS;
+      break;
+    case "sortby":
+      prefName = Preferences.SORT_ALIGNMENT; // id, etc.
+      break;
+    case "normalisesequencelogo":
+      prefName = Preferences.NORMALISE_CONSENSUS_LOGO;
+      break;
+    case "relaxedidmatch":
+      prefName = Preferences.RELAXEDSEQIDMATCHING;
+      break;
+    case "scaleproteinascdna":
+      prefName = Preferences.SCALE_PROTEIN_TO_CDNA;
+      break;
+    case "userdefinedcolour":
+      argName = "colour";
+      prefName = Preferences.USER_DEFINED_COLOURS;
+      break;
+    case "wrap":
+      prefName = Preferences.WRAP_ALIGNMENT;
+      break;
+    case "sortbytree":
+      argName = prefName;
+      prefName = Preferences.SORT_BY_TREE;
+      value = checkTF(value);
+      break;
+
+    // implemented; not tested:
+
+    case "pdbseq":
+    case "alignpdbfiles":
+      prefName = null;
+      break;
+    case "format":
+      argName = prefName;
+      break;
+    case "separator":
+      argName = prefName;
+      break;
+
+    // TODO: probably not relevant?
+
+    case "rgb":
+      prefName = null; // TODO no background for application?
+      break;
+    case "externalstructureviewer":
+      break;
+    case "application_url":
+      break;
+    case "automaticscrolling":
+      break;
+    case "heightscale":
+      break;
+    case "jalviewhelpurl":
+      break;
+    case "label":
+      break;
+    case "linklabel_":
+      prefName = "linkLabel_";
+      break;
+    case "linklabel_1":
+      prefName = "linkLabel_1";
+      break;
+    case "linkurl_":
+      prefName = "linkURL_";
+      break;
+
+    // unknown:
+
+    case "nojmol":
+    case "normaliselogo":
+    case "resolvetocodebase":
+    case "uppercase":
+    case "widthscale":
+    case "windowheight":
+    case "windowwidth":
+      prefName = null;
+      break;
+
+    // TRUE/FALSE
+
+    case "debug":
+    case "embedded":
+    case "showbutton":
+      value = checkTF(value);
+      break;
+    case "showannotation":
+      prefName = Preferences.SHOW_ANNOTATIONS;
+      value = checkTF(value);
+      break;
+    case "showconsensus":
+      prefName = Preferences.SHOW_CONSENSUS_LOGO;
+      value = checkTF(value);
+      break;
+    case "showconsensushistogram":
+      prefName = Preferences.SHOW_CONSENSUS_HISTOGRAM;
+      value = checkTF(value);
+      break;
+    case "showconservation":
+      prefName = Preferences.SHOW_CONSERVATION;
+      value = checkTF(value);
+      break;
+    case "showgroupconsensus":
+      prefName = Preferences.SHOW_GROUP_CONSENSUS;
+      value = checkTF(value);
+      break;
+    case "showgroupconservation":
+      prefName = Preferences.SHOW_GROUP_CONSERVATION;
+      value = checkTF(value);
+      break;
+    case "showoccupancy":
+      prefName = Preferences.SHOW_OCCUPANCY;
+      value = checkTF(value);
+      break;
+    case "showquality":
+      prefName = Preferences.SHOW_QUALITY;
+      value = checkTF(value);
+      break;
+    case "showsequencelogo":
+      prefName = Preferences.SHOW_CONSENSUS_LOGO;
+      value = checkTF(value);
+      break;
+    case "showunconserved":
+      prefName = Preferences.SHOW_UNCONSERVED;
+      value = checkTF(value);
+      break;
+    case "showfeaturegroups":
+    case "showfeaturesettings":
+    case "showfullid":
+    case "showtreebootstraps":
+    case "showtreedistances":
+    case "showunlinkedtreenodes":
+      value = checkTF(value);
+      break;
+    default:
+      if (appletName.startsWith("pdbfile")
+              || appletName.startsWith("sequence") && Character
+                      .isDigit(appletName.charAt(appletName.length() - 1)))
+      {
+        // could be pdbFile2, for example
+        prefName = argName = null;
+        value = resourcePath + value;
+        break;
       }
+      // or one of the app preference names
+      break;
+    }
+
+    // CASE I.  args[] name and value for ArgsParser
+    //
+    // If given an argument name, 
+    // put name and value into application args
+    if (value != null && argName != null)
+    {
+      vargs.add(argName);
+      if (value != "true")
+      {
+        vargs.add(value.toString());
+      }
+    }
+    
+    // CASE II.   applet parameter for JalviewJSApp
+    
+    if (value == null)
+    {
+      value = "false";
+    }
+    System.out.println("AppletParams propName=" + prefName + " argName="
+            + argName + " appletName=" + appletName + " value=" + value);    
+    if (appletName != null)
+    {
+      appletParams.put(appletName, value);
+    }
+    
+    // CASE III.  mapped to a Preference
+    
+    if (prefName != null)
+    {
+      Cache.setPropertyNoSave(prefName, value.toString());
     }
-    return appletParams;
   }
 
   /**
@@ -396,9 +428,9 @@ public class AppletParams extends HashMap<String, String>
    * @param value
    * @return "true" or null
    */
-  private static String checkTF(String value)
+  private static String checkTF(Object value)
   {
-    return (value.toLowerCase() == "true" ? "true" : null);
+    return (("" + value).toLowerCase() == "true" ? "true" : null);
   }
 
   /**
diff --git a/src/jalview/bin/ApplicationSingletonProvider.java b/src/jalview/bin/ApplicationSingletonProvider.java
new file mode 100644 (file)
index 0000000..b64f40c
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.bin;
+
+import jalview.util.Platform;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class to hold singleton objects, whose scope (context) is
+ * <ul>
+ * <li>the Java runtime (JVM) when running as Java</li>
+ * <li>one 'applet', when running as JalviewJS</li>
+ * </ul>
+ * This allows separation of multiple JS applets running on the same browser
+ * page, each with their own 'singleton' instances.
+ * <p>
+ * Instance objects are held in a separate Map (keyed by Class) for each
+ * context. For Java, this is just a single static Map. For SwingJS, the map is
+ * stored as a field {@code _swingjsSingletons} of
+ * {@code Thread.currentThread.getThreadGroup()}, as a proxy for the applet.
+ * <p>
+ * Note that when an applet is stopped, its ThreadGroup is removed, allowing any
+ * singleton references to be garbage collected.
+ * 
+ * @author hansonr
+ */
+public class ApplicationSingletonProvider
+{
+  /**
+   * A tagging interface to mark classes whose singleton instances may be served
+   * by {@code ApplicationSingletonProvider}, giving a distinct instance per JS
+   * 'applet'.
+   * <p>
+   * A class whose singleton should have global scope (be shared across all
+   * applets on a page) should <em>not</em> use this mechanism, but just provide
+   * a single instance (class static member) in the normal way.
+   */
+  public interface ApplicationSingletonI
+  {
+  }
+  
+  /*
+   * Map used to hold singletons in JVM context
+   */
+  private static Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> singletons = new HashMap<>();
+
+  /**
+   * private constructor for non-instantiable class
+   */
+  private ApplicationSingletonProvider()
+  {
+  }
+
+  /**
+   * Returns the singletons map for the current context (JVM for Java,
+   * ThreadGroup for JS), creating the map on the first request for each JS
+   * ThreadGroup
+   * 
+   * @return
+   */
+  private static Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> getContextMap()
+  {
+    @SuppressWarnings("unused")
+    ThreadGroup g = (Platform.isJS()
+            ? Thread.currentThread().getThreadGroup()
+            : null);
+    Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> map = singletons;
+    /** @j2sNative map = g._swingjsSingletons; */
+    if (map == null)
+    {
+      map = new HashMap<>();
+      /** @j2sNative g._swingjsSingletons = map; */
+    }
+
+    return map;
+  }
+
+  /**
+   * Answers the singleton instance of the given class for the current context
+   * (JVM or SwingJS 'applet'). If no instance yet exists, one is created, by
+   * calling the class's no-argument constructor. Answers null if any error
+   * occurs (or occurred previously for the same class).
+   * 
+   * @param c
+   * @return
+   */
+  public static ApplicationSingletonI getInstance(Class<? extends ApplicationSingletonI> c)
+  {
+    Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> map = getContextMap();
+    if (map.containsKey(c))
+    {
+      /*
+       * singleton already created _or_ creation failed (null value stored)
+       */
+      return map.get(c);
+    }
+
+    /*
+     * create and save the singleton
+     */
+    ApplicationSingletonI o = map.get(c);
+    try
+    {
+      Constructor<? extends ApplicationSingletonI> con = c
+              .getDeclaredConstructor();
+      con.setAccessible(true);
+      o = con.newInstance();
+    } catch (IllegalAccessException | InstantiationException
+            | IllegalArgumentException | InvocationTargetException
+            | NoSuchMethodException | SecurityException e)
+    {
+      Cache.log.error("Failed to create singleton for " + c.toString()
+              + ", error was: " + e.toString());
+      e.printStackTrace();
+    }
+
+    /*
+     * store the new singleton; note that a
+     * null value is saved if construction failed
+     */
+    getContextMap().put(c, o);
+    return o;
+  }
+
+  /**
+   * Removes the current singleton instance of the given class from the current
+   * application context. This has the effect of ensuring that a new instance is
+   * created the next time one is requested.
+   * 
+   * @param c
+   */
+  public static void removeInstance(
+          Class<? extends ApplicationSingletonI> c)
+  {
+    Map<Class<? extends ApplicationSingletonI>, ApplicationSingletonI> map = getContextMap();
+    if (map != null)
+    {
+      map.remove(c);
+    }
+  }
+}
index 91c8838..c5c08f6 100644 (file)
  */
 package jalview.bin;
 
+import jalview.util.Platform;
+
 import java.net.URLDecoder;
-import java.util.Vector;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Notes: this argParser does not distinguish between parameter switches,
@@ -96,7 +99,7 @@ public class ArgsParser
 
   public static final String VSESS = "vsess";
 
-  private Vector<String> vargs = null;
+  private List<String> vargs = null;
 
   private boolean isApplet;
 
@@ -109,7 +112,7 @@ public class ArgsParser
 
   public ArgsParser(String[] args)
   {
-    vargs = new Vector<>();
+    vargs = new ArrayList<>();
     isApplet = (args.length > 0 && args[0].startsWith("<applet"));
     if (isApplet)
     {
@@ -117,6 +120,13 @@ public class ArgsParser
     }
     else
     {
+      if (Platform.isJS())
+
+      {
+        isApplet = true;
+        appletParams = AppletParams
+                .getAppletParams(Platform.getAppletInfoAsMap(), vargs);
+      }
       for (int i = 0; i < args.length; i++)
       {
         String arg = args[i].trim();
@@ -124,7 +134,7 @@ public class ArgsParser
         {
           arg = arg.substring(1);
         }
-        vargs.addElement(arg);
+        vargs.add(arg);
       }
     }
   }
@@ -146,9 +156,9 @@ public class ArgsParser
     String dc = null, ret = null;
     if (index != -1)
     {
-      ret = vargs.elementAt(index + 1).toString();
-      vargs.removeElementAt(index);
-      vargs.removeElementAt(index);
+      ret = vargs.get(index + 1).toString();
+      vargs.remove(index);
+      vargs.remove(index);
       if (utf8decode && ret != null)
       {
         try
@@ -174,7 +184,7 @@ public class ArgsParser
   {
     if (vargs.contains(arg))
     {
-      vargs.removeElement(arg);
+      vargs.remove(arg);
       return true;
     }
     else
@@ -193,12 +203,13 @@ public class ArgsParser
     return vargs.size();
   }
 
-  public String getAppletValue(String key, String def)
+  public Object getAppletValue(String key, String def, boolean asString)
   {
-    String value;
+    Object value;
     return (appletParams == null ? null
-            : (value = appletParams.get(key.toLowerCase())) != null ? value
-                    : def);
+            : (value = appletParams.get(key.toLowerCase())) == null
+                    ? def : asString ? "" + value
+                    : value);
   }
 
 }
index 2c8986c..69f5f63 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.bin;
 
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.datamodel.PDBEntry;
 import jalview.gui.UserDefinedColours;
 import jalview.schemes.ColourSchemeLoader;
@@ -208,8 +209,26 @@ import org.apache.log4j.SimpleLayout;
  * @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
    */
@@ -228,10 +247,8 @@ public class Cache
   /**
    * 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";
@@ -239,8 +256,7 @@ public class Cache
   /**
    * 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
@@ -266,7 +282,7 @@ public class Cache
   public static Logger log;
 
   /** Jalview Properties */
-  private static Properties applicationProperties = new Properties()
+  private Properties applicationProperties = new Properties()
   {
     // override results in properties output in alphabetical order
     @Override
@@ -333,25 +349,18 @@ public class Cache
    */
   public static void loadProperties(String propsFile)
   {
-    /* for JalviewJS should be 
-    
-    getInstance().
-    
-   */
-    loadPropertiesImpl(propsFile);
-    
+
+    getInstance().loadPropertiesImpl(propsFile);
+
   }
 
-  private /* JalviewJS should not be static */
-  static //
-  void loadPropertiesImpl(String propsFile)
+  private void loadPropertiesImpl(String propsFile)
   {
-    
+
     propertiesFile = propsFile;
     if (propsFile == null && !propsAreReadOnly)
     {
-      propertiesFile = System.getProperty("user.home") + File.separatorChar
-              + ".jalview_properties";
+      propertiesFile = Platform.getUserPath(".jalview_properties");
     }
     else
     {
@@ -429,9 +438,9 @@ public class Cache
     }
     if (authorDetails == null)
     {
-        applicationProperties.remove("AUTHORS");
-        applicationProperties.remove("AUTHORFNAMES");
-        applicationProperties.remove("YEAR");
+      applicationProperties.remove("AUTHORS");
+      applicationProperties.remove("AUTHORFNAMES");
+      applicationProperties.remove("YEAR");
     }
 
     loadBuildProperties(false);
@@ -464,7 +473,8 @@ public class Cache
 
     String jnlpVersion = System.getProperty("jalview.version");
 
-    // jnlpVersion will be null if a latest version check for the channel needs to
+    // jnlpVersion will be null if a latest version check for the channel needs
+    // to
     // be done
     // Dont do this check if running in headless mode
 
@@ -557,8 +567,8 @@ public class Cache
         url = Cache.class.getResource(resourcePath).toString();
       } catch (Exception ex)
       {
-        System.err.println("Failed to resolve resource " + resourcePath + ": "
-                + ex.getMessage());
+        System.err.println("Failed to resolve resource " + resourcePath
+                + ": " + ex.getMessage());
       }
     }
     else
@@ -569,7 +579,7 @@ public class Cache
     return url;
   }
 
-  public static void loadBuildProperties(boolean reportVersion)
+  public void loadBuildProperties(boolean reportVersion)
   {
     String codeInstallation = getProperty("INSTALLATION");
     boolean printVersion = codeInstallation == null;
@@ -607,12 +617,12 @@ public class Cache
     new BuildDetails(codeVersion, null, codeInstallation);
     if (printVersion && reportVersion)
     {
-      System.out
-            .println("Jalview Version: " + codeVersion + codeInstallation);
+      System.out.println(
+              "Jalview Version: " + codeVersion + codeInstallation);
     }
   }
 
-  private static void deleteBuildProperties()
+  private void deleteBuildProperties()
   {
     applicationProperties.remove("LATEST_VERSION");
     applicationProperties.remove("VERSION");
@@ -634,17 +644,12 @@ public class Cache
    */
   public static String getProperty(String key)
   {
-    String prop =     /* for JalviewJS should be 
-    
-    getInstance().
-    
-   */
-            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;
   }
 
@@ -704,22 +709,7 @@ public class Cache
    */
   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);
   }
 
   /**
@@ -729,31 +719,24 @@ public class Cache
    */
   public static void removeProperty(String key)
   {
-    /* for JalviewJS should be 
-    
-    getInstance().
-    
-   */
-    removePropertyImpl(key, true);
+    getInstance().removePropertyImpl(key, true);
   }
 
   /**
    * Removes the named property for the running application, without saving the
    * properties file
    * 
-   * BH noting that ColourMenuHelper calls this. If the intent is to save, 
-   * then simply chanet that call to removeProperty(key).
+   * 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 removePropertyNoSave(String key)
   {
-    /* for JalviewJS should be 
-    
+
     getInstance().
-    
-   */
-    removePropertyImpl(key, false);
+
+            removePropertyImpl(key, false);
   }
 
   /**
@@ -763,9 +746,7 @@ public class Cache
    * @param key
    * @param andSave
    */
-  private /* JalviewJS should not be static */
-  static //
-  void removePropertyImpl(String key, boolean andSave)
+  private void removePropertyImpl(String key, boolean andSave)
   {
     applicationProperties.remove(key);
     if (andSave)
@@ -777,6 +758,15 @@ public class Cache
    */
   public static void saveProperties()
   {
+    getInstance().savePropertiesImpl();
+  }
+
+  /**
+   * save the properties to the jalview properties path
+   */
+  private void savePropertiesImpl()
+
+  {
     if (!propsAreReadOnly)
     {
       try
@@ -1028,9 +1018,9 @@ public class Cache
    * @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));
   }
 
   /**
@@ -1058,11 +1048,16 @@ public class Cache
   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 '"
@@ -1111,11 +1106,11 @@ public class Cache
     }
     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);
     }
   }
 
@@ -1160,17 +1155,12 @@ public class Cache
     {
       if (coloursFound.toString().length() > 1)
       {
-        /* for JalviewJS should be 
-        
-        getInstance().
-        
-       */
         setProperty(UserDefinedColours.USER_DEFINED_COLOURS,
                 coloursFound.toString());
       }
       else
       {
-        applicationProperties
+        getInstance().applicationProperties
                 .remove(UserDefinedColours.USER_DEFINED_COLOURS);
       }
     }
@@ -1201,16 +1191,20 @@ public class Cache
   public static String getVersionDetailsForConsole()
   {
     StringBuilder sb = new StringBuilder();
-    sb.append("Jalview Version: " + jalview.bin.Cache.getDefault("VERSION", "TEST"));
+    sb.append("Jalview Version: "
+            + jalview.bin.Cache.getDefault("VERSION", "TEST"));
     sb.append("\n");
     sb.append("Jalview Installation: "
             + jalview.bin.Cache.getDefault("INSTALLATION", "unknown"));
     sb.append("\n");
-    sb.append("Build Date: " + 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("\n");
-    sb.append(System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version"));
+    sb.append(System.getProperty("os.arch") + " "
+            + System.getProperty("os.name") + " "
+            + System.getProperty("os.version"));
     sb.append("\n");
     appendIfNotNull(sb, "Install4j version: ",
             System.getProperty("sys.install4jVersion"), "\n", null);
@@ -1218,7 +1212,9 @@ public class Cache
             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")) {
+    if (jalview.bin.Cache.getDefault("VERSION", "TEST")
+            .equals("DEVELOPMENT"))
+    {
       appendIfNotNull(sb, "Getdown appdir: ",
               System.getProperty("getdownappdir"), "\n", null);
       appendIfNotNull(sb, "Java home: ", System.getProperty("java.home"),
@@ -1237,10 +1233,11 @@ public class Cache
     // eg 'built from Source' or update channel
     return jalview.bin.Cache.getDefault("INSTALLATION", "unknown");
   }
-  
+
   /**
    * 
-   * For AppletParams and Preferences ok_actionPerformed and startupFileTextfield_mouseClicked
+   * For AppletParams and Preferences ok_actionPerformed and
+   * startupFileTextfield_mouseClicked
    * 
    * Sets a property value for the running application, without saving it to the
    * properties file
@@ -1250,12 +1247,7 @@ public class Cache
    */
   public static void setPropertyNoSave(String key, String obj)
   {
-    /* for JalviewJS should be 
-    
-    getInstance().
-    
-   */
-    setPropertyImpl(key, obj, false);
+    getInstance().setPropertyImpl(key, obj, false);
   }
 
   /**
@@ -1267,7 +1259,8 @@ public class Cache
    * @param andSave
    * @return
    */
-  private /* for JalviewJS should not be static */ static Object setPropertyImpl(String key, String obj, boolean andSave)
+  private Object setPropertyImpl(
+          String key, String obj, boolean andSave)
   {
     Object oldValue = null;
     try
@@ -1287,6 +1280,4 @@ public class Cache
     return oldValue;
   }
 
-  
-  
 }
index e98ea63..4a5c733 100755 (executable)
@@ -37,7 +37,6 @@ import java.security.PermissionCollection;
 import java.security.Permissions;
 import java.security.Policy;
 import java.util.HashMap;
-import java.util.Hashtable;
 import java.util.Map;
 import java.util.Vector;
 
@@ -49,23 +48,13 @@ import com.threerings.getdown.util.LaunchUtil;
 import groovy.lang.Binding;
 import groovy.util.GroovyScriptEngine;
 import jalview.api.AlignCalcWorkerI;
-import jalview.api.AlignViewportI;
-import jalview.api.JalviewApp;
-import jalview.api.StructureSelectionManagerProvider;
-import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.PDBEntry;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.ext.so.SequenceOntology;
 import jalview.gui.AlignFrame;
 import jalview.gui.AlignViewport;
-import jalview.gui.AlignmentPanel;
-import jalview.gui.CalculationChooser;
 import jalview.gui.Desktop;
 import jalview.gui.Preferences;
 import jalview.gui.PromptUserConfig;
-import jalview.gui.StructureViewer;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.BioJsHTMLOutput;
 import jalview.io.DataSourceType;
@@ -78,17 +67,12 @@ import jalview.io.HtmlSvgOutput;
 import jalview.io.IdentifyFile;
 import jalview.io.NewickFile;
 import jalview.io.gff.SequenceOntologyFactory;
-import jalview.javascript.JSFunctionExec;
-import jalview.javascript.MouseOverStructureListener;
-import jalview.renderer.seqfeatures.FeatureRenderer;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
-import jalview.structure.SelectionSource;
-import jalview.structure.VamsasSource;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.ws.jws2.Jws2Discoverer;
-import netscape.javascript.JSObject;
+//import netscape.javascript.JSObject;
 
 /**
  * Main class for Jalview Application <br>
@@ -105,30 +89,44 @@ import netscape.javascript.JSObject;
  * @author $author$
  * @version $Revision$
  */
-public class Jalview implements JalviewJSApi
+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()
   {
-    Platform.getURLCommandArguments();
+    return (Jalview) ApplicationSingletonProvider
+            .getInstance(Jalview.class);
   }
 
-  private boolean headless;
-
-  // singleton instance of this class
+  private Jalview()
+  {
+  }
 
-  private static Jalview instance;
+  private boolean headless;
 
   private Desktop desktop;
 
-  public static AlignFrame currentAlignFrame;
-
-  public boolean isJavaAppletTag;
+  public AlignFrame currentAlignFrame;
 
   public String appletResourcePath;
 
-  JalviewAppLoader appLoader;
-
-  protected JSFunctionExec jsFunctionExec;
+  public String j2sAppletID;
 
   private boolean noCalculation, noMenuBar, noStatus;
 
@@ -156,12 +154,9 @@ public class Jalview implements JalviewJSApi
 
   static
   {
-    if (!Platform.isJS())
-    /**
-     * Java only
-     * 
-     * @j2sIgnore
-     */
+    if (Platform.isJS()) {
+        Platform.getURLCommandArguments();
+    } else /** @j2sIgnore */
     {
       // grab all the rights we can for the JVM
       Policy.setPolicy(new Policy()
@@ -241,12 +236,6 @@ public class Jalview implements JalviewJSApi
 
   }
 
-  public static Jalview getInstance()
-  {
-    return instance;
-  }
-  
-  
   private final static boolean doPlatformLogging = false;
 
   /**
@@ -261,9 +250,7 @@ public class Jalview implements JalviewJSApi
     {
       Platform.startJavaLogging();
     }
-
-    instance = new Jalview();
-    instance.doMain(args);
+    getInstance().doMain(args);
   }
 
   /**
@@ -273,11 +260,7 @@ public class Jalview implements JalviewJSApi
   {
 
     boolean isJS = Platform.isJS();
-    if (isJS)
-    {
-      Platform.setAppClass(this);
-    }
-    else
+    if (!isJS)
     {
       System.setSecurityManager(null);
     }
@@ -305,7 +288,7 @@ public class Jalview implements JalviewJSApi
     }
 
     // report Jalview version
-    Cache.loadBuildProperties(true);
+    Cache.getInstance().loadBuildProperties(true);
 
     ArgsParser aparser = new ArgsParser(args);
     headless = false;
@@ -314,18 +297,16 @@ public class Jalview implements JalviewJSApi
 
     Cache.loadProperties(usrPropsFile); // must do this before
 
+    boolean allowServices = true;
+    
     if (isJS)
     {
-      isJavaAppletTag = aparser.isApplet();
-      if (isJavaAppletTag)
-      {
-        Preferences.setAppletDefaults();
-        Cache.loadProperties(usrPropsFile); // again, because we
-        // might be changing defaults here?
-      }
-      System.out.println(
-              "<Applet> found: " + aparser.getValue("Info.j2sAppletID"));
-      appletResourcePath = aparser.getValue("Info.resourcePath");
+      j2sAppletID = Platform.getAppID(null);
+      Preferences.setAppletDefaults();
+      Cache.loadProperties(usrPropsFile); // again, because we
+      // might be changing defaults here?
+      appletResourcePath = (String) aparser.getAppletValue("resourcepath",
+              null, true);
     }
     else
     /**
@@ -357,11 +338,12 @@ public class Jalview implements JalviewJSApi
       // anything else!
 
       final String jabawsUrl = aparser.getValue(ArgsParser.JABAWS);
-      if (jabawsUrl != null)
+      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)
@@ -408,8 +390,6 @@ public class Jalview implements JalviewJSApi
       System.exit(0);
     }
 
-    desktop = null;
-
     if (!isJS)
     /** @j2sIgnore */
     {
@@ -464,17 +444,18 @@ public class Jalview implements JalviewJSApi
      * 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.setInstance(new SequenceOntology());
+      SequenceOntologyFactory.setSequenceOntology(new SequenceOntology());
     }
 
+
+    desktop = null;
     if (!headless)
     {
-      desktop = new Desktop();
+      desktop = Desktop.getInstance();
       desktop.setInBatchMode(true); // indicate we are starting up
-
       try
       {
         JalviewTaskbar.setTaskbar(this);
@@ -482,17 +463,17 @@ public class Jalview implements JalviewJSApi
       {
         System.out.println("Error setting Taskbar: " + t.getMessage());
       }
-
       desktop.setVisible(true);
 
-      if (!Platform.isJS())
+      if (!isJS)
       /**
        * Java only
        * 
        * @j2sIgnore
        */
       {
-        desktop.startServiceDiscovery();
+        if (allowServices)
+          desktop.startServiceDiscovery();
         if (!aparser.contains("nousagestats"))
         {
           startUsageStats(desktop);
@@ -544,37 +525,20 @@ public class Jalview implements JalviewJSApi
         BioJsHTMLOutput.updateBioJS();
       }
     }
-
     parseArguments(aparser, true);
   }
 
   /**
-   * Allow an outside entity to initiate the second half of argument parsing
-   * (only).
+   * Parse all command-line String[] arguments as well as all JavaScript-derived
+   * parameters from Info.
    * 
-   * @param args
-   * @return null is good
-   */
-  @Override
-  public Object parseArguments(String[] args)
-  {
-
-    try
-    {
-      parseArguments(new ArgsParser(args), false);
-      return null;
-    } catch (Throwable t)
-    {
-      return t;
-    }
-  }
-
-  /**
+   * We allow for this method to be run from JavaScript. Basically allowing
+   * simple scripting.
    * 
    * @param aparser
    * @param isStartup
    */
-  private void parseArguments(ArgsParser aparser, boolean isStartup)
+  public void parseArguments(ArgsParser aparser, boolean isStartup)
   {
 
     String groovyscript = null; // script to execute after all loading is
@@ -609,18 +573,82 @@ public class Jalview implements JalviewJSApi
 
     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)
       {
@@ -651,54 +679,30 @@ public class Jalview implements JalviewJSApi
           }
         }
       }
-      String fileFormat = (isJavaAppletTag
-              ? aparser.getAppletValue("format", null)
+      
+      String fileFormat = (isJS
+              ? (String) aparser.getAppletValue("format", null, true)
               : null);
       protocol = AppletFormatAdapter.checkProtocol(file);
       try
       {
-        format = (isJavaAppletTag && fileFormat != null
+        format = (fileFormat != null
                 ? FileFormats.getInstance().forName(fileFormat)
                 : null);
         if (format == null)
         {
           format = new IdentifyFile().identify(file, protocol);
         }
-        format = new IdentifyFile().identify(file, protocol);
       } catch (FileFormatException e1)
       {
         // TODO ?
       }
 
-      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!");
-      }
-
-      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 af = new FileLoader(!headless).LoadFileWaitTillLoaded(file,
-              protocol, format);
+      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
       {
@@ -733,135 +737,13 @@ public class Jalview implements JalviewJSApi
         }
         setCurrentAlignFrame(af);
 
-        String data = aparser.getValue(ArgsParser.COLOUR, true);
-        if (data != null)
-        {
-          data.replaceAll("%20", " ");
-
-          ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
-                  af.getViewport(), af.getViewport().getAlignment(), data);
-
-          if (cs != null)
-          {
-            System.out.println(
-                    "CMD [-color " + data + "] executed successfully!");
-          }
-          af.changeColour(cs);
-        }
-
-        // 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!");
-        }
-
-        // 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())
-          {
-            System.out.println("CMD [-sortbytree] executed successfully!");
-          }
-        }
-
-        boolean doUpdateAnnotation = false;
-        /**
-         * we do this earlier in JalviewJS because of a complication with
-         * SHOWOVERVIEW
-         * 
-         * For now, just fixing this in JalviewJS.
-         *
-         * 
-         * @j2sIgnore
-         * 
-         */
-        {
-          if (aparser.contains(ArgsParser.NOANNOTATION)
-                  || aparser.contains(ArgsParser.NOANNOTATION2))
-          {
-            af.getViewport().setShowAnnotation(false);
-            if (!af.getViewport().isShowAnnotation())
-            {
-              doUpdateAnnotation = true;
-              System.out
-                      .println("CMD no-annotation executed successfully!");
-            }
-          }
-        }
+        setFrameDependentProperties(aparser, af);
         
-        
-        if (aparser.contains(ArgsParser.NOSORTBYTREE))
-        {
-          af.getViewport().setSortByTree(false);
-          if (!af.getViewport().getSortByTree())
-          {
-            doUpdateAnnotation = true;
-            System.out
-                    .println("CMD [-nosortbytree] executed successfully!");
-          }
-        }
-        if (doUpdateAnnotation)
-        { // BH 2019.07.24
-          af.setMenusForViewport();
-          af.alignPanel.updateLayout();
-        }
-
-        data = aparser.getValue(ArgsParser.TREE, true);
-        if (data != null)
-        {
-          try
-          {
-            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);
-          }
-        }
-        // TODO - load PDB structure(s) to alignment JAL-629
-        // (associate with identical sequence in alignment, or a specified
-        // sequence)
-        if (isJavaAppletTag)
+        if (isJS)
         {
-          loadAppletParams(aparser, af);
+          jsApp.initFromParams(af);
         }
-        else if (!isJS)
+        else
         /**
          * Java only
          * 
@@ -880,75 +762,19 @@ public class Jalview implements JalviewJSApi
             groovyscript = null;
           }
         }
-        createOutputFiles(aparser, af, format);
-        while (aparser.getSize() > 0)
-        {
-          System.out.println("Unknown arg: " + aparser.nextValue());
-        }
-      }
-    }
-    
-    
-    AlignFrame startUpAlframe = null;
-    // We'll only open the default file if the desktop is visible.
-    // And the user
-    // ////////////////////
-
-    if (!isJS && !headless && file == null
-            && Cache.getDefault("SHOW_STARTUP_FILE", true))
-    /**
-     * Java only
-     * 
-     * @j2sIgnore
-     */
-    {
-      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?
+        if (!isJS || !isStartup) {
+          createOutputFiles(aparser, format);
         }
       }
-
-      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 (!isJS && groovyscript != null)
     {
       if (Cache.groovyJarsPresent())
       {
         System.out.println("Executing script " + groovyscript);
-        executeGroovyScript(groovyscript, startUpAlframe);
+        executeGroovyScript(groovyscript, af);
       }
       else
       {
@@ -967,67 +793,220 @@ public class Jalview implements JalviewJSApi
       }
       desktop.setInBatchMode(false);
     }
+    
+    if (jsApp != null) {
+      jsApp.callInitCallback();
+    }
   }
-
+  
   /**
-   * 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.
+   * Set general display parameters irrespective of file loading or headlessness.
    * 
    * @param aparser
-   * @param af
-   * @param format
    */
-  private void createOutputFiles(ArgsParser aparser, AlignFrame af,
-          FileFormatI format)
+  private void setDisplayParameters(ArgsParser aparser)
   {
-    String imageName = "unnamed.png";
-    while (aparser.getSize() > 1)
+    if (aparser.contains(ArgsParser.NOMENUBAR))
     {
-      String outputFormat = aparser.nextValue();
-      String file = aparser.nextValue();
+      noMenuBar = true;
+      System.out.println("CMD [nomenu] 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"))
+    if (aparser.contains(ArgsParser.NOSTATUS))
+    {
+      noStatus = true;
+      System.out.println("CMD [nostatus] executed successfully!");
+    }
+
+    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!");
+    }
+  }
+
+
+  private void setFrameDependentProperties(ArgsParser aparser,
+          AlignFrame af)
+  {
+    String data = aparser.getValue(ArgsParser.COLOUR, true);
+    if (data != null)
+    {
+      data.replaceAll("%20", " ");
+
+      ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
+              af.getViewport(), af.getViewport().getAlignment(), data);
+
+      if (cs != null)
       {
-        File imageFile = new File(file);
-        imageName = imageFile.getName();
-        af.createSVG(imageFile);
-        System.out.println("Creating SVG image: " + file);
-        continue;
+        System.out.println(
+                "CMD [-color " + data + "] executed successfully!");
       }
-      else if (outputFormat.equalsIgnoreCase("html"))
+      af.changeColour(cs);
+    }
+
+    // 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!");
+    }
+
+    // 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())
       {
-        File imageFile = new File(file);
-        imageName = imageFile.getName();
-        HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
-        htmlSVG.exportHTML(file);
-        System.out.println("Creating HTML image: " + file);
-        continue;
+        System.out.println("CMD [-sortbytree] executed successfully!");
       }
-      else if (outputFormat.equalsIgnoreCase("biojsmsa"))
-      {
-        if (file == null)
-        {
-          System.err.println("The output html file must not be null");
-          return;
-        }
+    }
+
+    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)
+      {
+        af.getViewport().setShowAnnotation(false);
+        if (!af.getViewport().isShowAnnotation())
+        {
+          doUpdateAnnotation = true;
+        }
+      }
+    }
+
+    if (aparser.contains(ArgsParser.NOSORTBYTREE))
+    {
+      af.getViewport().setSortByTree(false);
+      if (!af.getViewport().getSortByTree())
+      {
+        doUpdateAnnotation = true;
+        System.out
+                .println("CMD [-nosortbytree] executed successfully!");
+      }
+    }
+    if (doUpdateAnnotation)
+    { // BH 2019.07.24
+      af.setMenusForViewport();
+      af.alignPanel.updateLayout();
+    }
+
+    data = aparser.getValue(ArgsParser.TREE, true);
+    if (data != null)
+    {
+      try
+      {
+        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);
+      }
+    }
+    // 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)
+  {
+    AlignFrame af = currentAlignFrame;
+    while (aparser.getSize() >= 2)
+    {
+      String outputFormat = aparser.nextValue();
+      File imageFile;
+      String fname;
+      switch (outputFormat.toLowerCase())
+      {
+      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(
@@ -1037,39 +1016,45 @@ public class Jalview implements JalviewJSApi
           e.printStackTrace();
         }
         BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
-        bjs.exportHTML(file);
-        System.out.println("Creating BioJS MSA Viwer HTML file: " + file);
+        bjs.exportHTML(fname);
+        System.out.println("Creating BioJS MSA Viwer HTML file: " + fname);
         continue;
-      }
-      else if (outputFormat.equalsIgnoreCase("imgMap"))
-      {
-        af.createImageMap(new File(file), imageName);
-        System.out.println("Creating image map: " + file);
+      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;
-      }
-      else if (outputFormat.equalsIgnoreCase("eps"))
-      {
-        File outputFile = new File(file);
+      case "imgmap":
+        imageFile = new File(aparser.nextValue());
+        af.alignPanel.makePNGImageMap(imageFile, "unnamed.png");
         System.out.println(
-                "Creating EPS file: " + outputFile.getAbsolutePath());
-        af.createEPS(outputFile);
+                "Creating image map: " + imageFile.getAbsolutePath());
         continue;
       }
-
-      af.saveAlignment(file, format);
-      if (af.isSaveAlignmentSuccessful())
+      if (!Platform.isJS()) /** @j2sIgnore */
       {
-        System.out.println(
-                "Written alignment in " + format + " format to " + file);
-      }
-      else
-      {
-        System.out.println("Error writing file " + file + " in " + format
-                + " format!!");
+        // skipping outputFormat?
+        System.out.println("Unknown arg: " + outputFormat);      
+        fname = new File(aparser.nextValue()).getAbsolutePath();
+        af.saveAlignment(fname, format);
+        if (af.isSaveAlignmentSuccessful())
+        {
+          System.out.println(
+                  "Written alignment in " + format + " format to " + fname);
+        }
+        else
+        {
+          System.out.println("Error writing file " + fname + " in " + format
+                  + " format!!");
+        }
       }
-
+      break;
+    }
+    while (aparser.getSize() > 0)
+    {
+      System.out.println("Unknown arg: " + aparser.nextValue());
     }
-
   }
 
   private static void showUsage()
@@ -1118,7 +1103,7 @@ public class Jalview implements JalviewJSApi
     /**
      * 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 ?"
@@ -1297,813 +1282,32 @@ public class Jalview implements JalviewJSApi
 
   public static AlignFrame getCurrentAlignFrame()
   {
-    return Jalview.currentAlignFrame;
+    return Jalview.getInstance().currentAlignFrame;
   }
 
   public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
   {
-    Jalview.currentAlignFrame = currentAlignFrame;
-  }
-
-  /**
-   * 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 = Cache.getProperty("Info.j2sAppletID");
-    if (id == null)
-    {
-      id = "jalview";
-    }
-    return id + (frameType == null ? "" : "-" + frameType);
-  }
-
-  /**
-   * Handle all JalviewLite applet parameters
-   * 
-   * @param aparser
-   * @param af
-   */
-  private void loadAppletParams(ArgsParser aparser, AlignFrame af)
-  {
-    JalviewApp app = new JalviewApp()
-    {
-
-      // TODO BH 2019
-      //
-      // These are methods that are in JalviewLite that various classes call
-      // but are not in JalviewLiteJsApi. Or, even if they are, other classes
-      // call
-      // them to JalviewLite directly. Some may not be necessary, but they have
-      // to
-      // be at least mentioned here, or the classes calling them should
-      // reference
-      // JalviewLite itself.
-
-      private boolean alignPDBStructures; // From JalviewLite; not implemented
-
-      private Hashtable<String, Hashtable<String, String[]>> jsmessages;
-
-      private Hashtable<String, int[]> jshashes;
-
-      @Override
-      public String getParameter(String name)
-      {
-        return aparser.getAppletValue(name, null);
-      }
-
-      @Override
-      public boolean getDefaultParameter(String name, boolean def)
-      {
-        String stn;
-        return ((stn = getParameter(name)) == null ? def
-                : "true".equalsIgnoreCase(stn));
-      }
-
-      /**
-       * Get the applet-like document base even though this is an application.
-       */
-      @Override
-      public URL getDocumentBase()
-      {
-        return Platform.getDocumentBase();
-      }
-
-      /**
-       * Get the applet-like code base even though this is an application.
-       */
-      @Override
-      public URL getCodeBase()
-      {
-        return Platform.getCodeBase();
-      }
-
-      @Override
-      public AlignViewportI getViewport()
-      {
-        return af.getViewport();
-      }
-
-      /**
-       * features
-       * 
-       */
-      @Override
-      public boolean parseFeaturesFile(String filename,
-              DataSourceType protocol)
-      {
-        return af.parseFeaturesFile(filename, protocol);
-      }
-
-      /**
-       * scorefile
-       * 
-       */
-      @Override
-      public boolean loadScoreFile(String sScoreFile) throws IOException
-      {
-        af.loadJalviewDataFile(sScoreFile, null, null, null);
-        return true;
-      }
-
-      /**
-       * annotations, jpredfile, jnetfile
-       * 
-       */
-      @Override
-      public void updateForAnnotations()
-      {
-        af.updateForAnnotations();
-      }
-
-      @Override
-      public void loadTree(NewickFile fin, String treeFile)
-              throws IOException
-      {
-        // n/a -- already done by standard Jalview command line processing
-      }
-
-      @Override
-      public void setAlignPdbStructures(boolean defaultParameter)
-      {
-        alignPDBStructures = true;
-      }
-
-      @Override
-      public void newStructureView(PDBEntry pdb, SequenceI[] seqs,
-              String[] chains, DataSourceType protocol)
-      {
-        StructureViewer.launchStructureViewer(af.alignPanel, pdb, seqs);
-      }
-
-      @Override
-      public void setFeatureGroupState(String[] groups, boolean state)
-      {
-        af.setFeatureGroupState(groups, state);
-      }
-
-      @Override
-      public void alignedStructureView(PDBEntry[] pdb, SequenceI[][] seqs,
-              String[][] chains, String[] protocols)
-      {
-        System.err.println(
-                "Jalview applet interface alignedStructureView not implemented");
-      }
-
-      @Override
-      public void newFeatureSettings()
-      {
-        System.err.println(
-                "Jalview applet interface newFeatureSettings not implemented");
-      }
-
-      private Vector<Runnable> jsExecQueue;
-
-      @Override
-      public Vector<Runnable> getJsExecQueue(JSFunctionExec exec)
-      {
-        jsFunctionExec = exec;
-        return (jsExecQueue == null ? (jsExecQueue = new Vector<>())
-                : jsExecQueue);
-      }
-
-// AppletContext deprecated 
-//
-//      @Override
-//      public AppletContext getAppletContext()
-//      {
-//        // TODO Auto-generated method stub
-//        return null;
-//      }
-//
-      @Override
-      public boolean isJsfallbackEnabled()
-      {
-        // TODO Auto-generated method stub
-        return false;
-      }
-
-      @Override
-      public JSObject getJSObject()
-      {
-        // TODO Auto-generated method stub
-        return null;
-      }
-
-      @Override
-      public StructureSelectionManagerProvider getStructureSelectionManagerProvider()
-      {
-        // TODO Q: what exactly is this? BH
-        return null;
-      }
-
-      @Override
-      public void updateColoursFromMouseOver(Object source,
-              MouseOverStructureListener mouseOverStructureListener)
-      {
-        // TODO Auto-generated method stub
-
-      }
-
-      @Override
-      public Object[] getSelectionForListener(SequenceGroup seqsel,
-              ColumnSelection colsel, HiddenColumns hidden,
-              SelectionSource source, Object alignFrame)
-      {
-        return appLoader.getSelectionForListener(getCurrentAlignFrame(),
-                seqsel, colsel, hidden, source, alignFrame);
-      }
-
-      @Override
-      public String arrayToSeparatorList(String[] array)
-      {
-        return appLoader.arrayToSeparatorList(array);
-      }
-
-      @Override
-      public Hashtable<String, int[]> getJSHashes()
-      {
-        return (jshashes == null ? (jshashes = new Hashtable<>())
-                : jshashes);
-      }
-
-      @Override
-      public Hashtable<String, Hashtable<String, String[]>> getJSMessages()
-      {
-        return (jsmessages == null ? (jsmessages = new Hashtable<>())
-                : jsmessages);
-      }
-
-      @Override
-      public Object getFrameForSource(VamsasSource source)
-      {
-        if (source != null)
-        {
-          AlignFrame af;
-          if (source instanceof jalview.gui.AlignViewport
-                  && source == (af = getCurrentAlignFrame()).getViewport())
-          {
-            // should be valid if it just generated an event!
-            return af;
-          }
-          // TODO: ensure that if '_af' is specified along with a handler
-          // function, then only events from that alignFrame are sent to that
-          // function
-        }
-        return null;
-      }
-
-      @Override
-      public FeatureRenderer getNewFeatureRenderer(AlignViewportI vp)
-      {
-        return new jalview.gui.FeatureRenderer((AlignmentPanel) vp);
-      }
-
-    };
-
-    appLoader = new JalviewAppLoader(true);
-    appLoader.load(app);
-  }
-
-  /**
-   * 
-   * @see jalview.bin.JalviewLiteJsApi#getSelectedSequences()
-   */
-  @Override
-  public String getSelectedSequences()
-  {
-    return getSelectedSequencesFrom(getCurrentAlignFrame());
-  }
-
-  /**
-   * 
-   * @see jalview.bin.JalviewLiteJsApi#getSelectedSequences(java.lang.String)
-   */
-  @Override
-  public String getSelectedSequences(String sep)
-  {
-    return getSelectedSequencesFrom(getCurrentAlignFrame(), sep);
-  }
-
-  /**
-   * 
-   * @see jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
-   *      .AlignFrame)
-   */
-  @Override
-  public String getSelectedSequencesFrom(AlignFrame alf)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return getSelectedSequencesFrom(alf, null);
-  }
-
-  /**
-   * 
-   * @see jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
-   *      .AlignFrame, java.lang.String)
-   */
-  @Override
-  public String getSelectedSequencesFrom(AlignFrame alf, String sep)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return appLoader.getSelectedSequencesFrom(alf, sep);
+    Jalview.getInstance().currentAlignFrame = currentAlignFrame;
   }
 
-  /**
-   * 
-   * @see jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
-   *      .AlignFrame, java.lang.String)
-   */
-  @Override
-  public void highlight(String sequenceId, String position,
-          String alignedPosition)
-  {
-    highlightIn(null, sequenceId, position, alignedPosition);
-  }
-
-  @Override
-  public void highlightIn(AlignFrame alf, String sequenceId,
-          String position, String alignedPosition)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    appLoader.highlightIn(alf, sequenceId, position, alignedPosition);
-  }
-
-  @Override
-  public void select(String sequenceIds, String columns)
-  {
-    selectIn(getCurrentAlignFrame(), sequenceIds, columns, null);
-  }
-
-  @Override
-  public void select(String sequenceIds, String columns, String sep)
-  {
-    selectIn(null, sequenceIds, columns, sep);
-  }
-
-  @Override
-  public void selectIn(AlignFrame alf, String sequenceIds, String columns)
-  {
-    selectIn(alf, sequenceIds, columns, null);
-  }
-
-  @Override
-  public void selectIn(AlignFrame alf, String sequenceIds, String columns,
-          String sep)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    appLoader.selectIn(alf, sequenceIds, columns, sep);
-  }
-
-  @Override
-  public String getSelectedSequencesAsAlignment(String format,
-          String suffix)
-  {
-    return getSelectedSequencesAsAlignmentFrom(null, format, suffix);
-  }
-
-  @Override
-  public String getSelectedSequencesAsAlignmentFrom(AlignFrame alf,
-          String format, String sep)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return appLoader.getSelectedSequencesAsAlignmentFrom(alf, format, sep);
-  }
-
-  @Override
-  public String getAlignmentOrder()
-  {
-    return getAlignmentFrom(getCurrentAlignFrame(), null);
-  }
-
-  @Override
-  public String getAlignmentOrderFrom(AlignFrame alf)
-  {
-    return getAlignmentFrom(alf, null);
-  }
-
-  @Override
-  public String getAlignmentOrderFrom(AlignFrame alf, String sep)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return appLoader.getAlignmentOrderFrom(alf, sep);
-  }
-
-  @Override
-  public String orderBy(String order, String undoName)
-  {
-    return orderBy(order, undoName, null);
-  }
-
-  @Override
-  public String orderBy(String order, String undoName, String sep)
-  {
-    return orderAlignmentBy(getCurrentAlignFrame(), order, undoName, sep);
-  }
-
-  @Override
-  public String orderAlignmentBy(AlignFrame alf, String order,
-          String undoName, String sep)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return appLoader.orderAlignmentBy(alf, order, undoName, sep);
-  }
-
-  @Override
-  public String getAlignment(String format)
-  {
-    return getAlignmentFrom(null, format, null);
-  }
-
-  @Override
-  public String getAlignmentFrom(AlignFrame alf, String format)
-  {
-    return getAlignmentFrom(alf, format, null);
-  }
-
-  @Override
-  public String getAlignment(String format, String suffix)
-  {
-    return getAlignmentFrom(getCurrentAlignFrame(), format, suffix);
-  }
-
-  @Override
-  public String getAlignmentFrom(AlignFrame alf, String format,
-          String suffix)
-  {
-    return appLoader.getAlignmentFrom(alf, format, suffix);
-  }
-
-  @Override
-  public void loadAnnotation(String annotation)
-  {
-    loadAnnotationFrom(getCurrentAlignFrame(), annotation);
-  }
-
-  @Override
-  public void loadAnnotationFrom(AlignFrame alf, String annotation)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    appLoader.loadAnnotationFrom(alf, annotation);
-  }
-
-  @Override
-  public void loadFeatures(String features, boolean autoenabledisplay)
-  {
-    loadFeaturesFrom(currentAlignFrame, features, autoenabledisplay);
-  }
-
-  @Override
-  public boolean loadFeaturesFrom(AlignFrame alf, String features,
-          boolean autoenabledisplay)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return appLoader.loadFeaturesFrom(alf, features, autoenabledisplay);
-  }
-
-  @Override
-  public String getFeatures(String format)
-  {
-    return getFeaturesFrom(null, format);
-  }
-
-  @Override
-  public String getFeaturesFrom(AlignFrame alf, String format)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return appLoader.getFeaturesFrom(alf, format);
-  }
-
-  @Override
-  public String getAnnotation()
-  {
-    return getAnnotationFrom(null);
-  }
-
-  @Override
-  public String getAnnotationFrom(AlignFrame alf)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return appLoader.getAnnotationFrom(alf);
-  }
-
-  // @Override
-  // public AlignFrame newView()
-  // {
-  // return newViewFrom(null, null);
-  // }
-  //
-  // @Override
-  // public AlignFrame newView(String name)
-  // {
-  // return newViewFrom(null, name);
-  // }
-  //
-  // @Override
-  // public AlignFrame newViewFrom(AlignFrame alf)
-  // {
-  // return newViewFrom(alf, null);
-  // }
-  //
-  // @Override
-  // public AlignFrame newViewFrom(AlignFrame alf, String name)
-  // {
-  // if (alf == null)
-  // {
-  // alf = getCurrentAlignFrame();
-  // }
-  // return appLoader.newViewFrom(alf, name);
-  // }
-
-  @Override
-  public AlignFrame loadAlignment(String text, String title)
-  {
-    return appLoader.loadAlignment(text, AlignFrame.DEFAULT_WIDTH,
-            AlignFrame.DEFAULT_HEIGHT, title);
-  }
-
-  @Override
-  public boolean addPdbFile(AlignFrame alFrame, String sequenceId,
-          String pdbEntryString, String pdbFile)
-  {
-    if (alFrame == null)
-    {
-      alFrame = getCurrentAlignFrame();
-    }
-    return appLoader.addPdbFile(alFrame, sequenceId, pdbEntryString,
-            pdbFile);
-  }
-
-  @Override
-  public void scrollViewToIn(AlignFrame alf, String topRow,
-          String leftHandColumn)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    appLoader.scrollViewToIn(alf, topRow, leftHandColumn);
-  }
-
-  @Override
-  public void scrollViewToRowIn(AlignFrame alf, String topRow)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    appLoader.scrollViewToRowIn(alf, topRow);
-  }
-
-  @Override
-  public void scrollViewToColumnIn(AlignFrame alf, String leftHandColumn)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    appLoader.scrollViewToColumnIn(alf, leftHandColumn);
-  }
-
-  @Override
-  public String getFeatureGroups()
-  {
-    return getFeatureGroupsOn(null);
-  }
-
-  @Override
-  public String getFeatureGroupsOn(AlignFrame alf)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return appLoader.getFeatureGroupsOn(alf);
-  }
-
-  @Override
-  public String getFeatureGroupsOfState(boolean visible)
-  {
-    return getFeatureGroupsOfStateOn(null, visible);
-  }
-
-  @Override
-  public String getFeatureGroupsOfStateOn(AlignFrame alf, boolean visible)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return appLoader.getFeatureGroupsOfStateOn(alf, visible);
-  }
-
-  @Override
-  public void setFeatureGroupState(String groups, boolean state)
-  { // JalviewLite API
-    setFeatureGroupStateOn(null, groups, state);
-  }
-
-  @Override
-  public void setFeatureGroupStateOn(AlignFrame alf, String groups,
-          boolean state)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    appLoader.setFeatureGroupStateOn(alf, groups, state);
-  }
-
-  @Override
-  public String getSeparator()
-  {
-    return appLoader.getSeparator();
-  }
-
-  @Override
-  public void setSeparator(String separator)
-  {
-    appLoader.setSeparator(separator);
-  }
-
-  @Override
-  public String getJsMessage(String messageclass, String viewId)
-  {
-    // see http://www.jalview.org/examples/jalviewLiteJs.html
-    return null;
-  }
-
-  /**
-   * Open a new Tree panel on the desktop statically. Params are standard (not
-   * set by Groovy). No dialog is opened.
-   * 
-   * @param af
-   * @param treeType
-   * @param modelName
-   * @return null, or the string "label.you_need_at_least_n_sequences" if number
-   *         of sequences selected is inappropriate
-   */
-  @Override
-  public Object openTreePanel(AlignFrame af, String treeType,
-          String modelName)
-  { // JalviewJS api
-    if (af == null)
-    {
-      af = getCurrentAlignFrame();
-    }
-    return CalculationChooser.openTreePanel(af, treeType, modelName, null);
-  }
-
-  /**
-   * public static method for JalviewJS API to open a PCAPanel without
-   * necessarily using a dialog.
-   * 
-   * @param af
-   * @param modelName
-   * @return the PCAPanel, or the string "label.you_need_at_least_n_sequences"
-   *         if number of sequences selected is inappropriate
-   */
-  @Override
-  public Object openPcaPanel(AlignFrame af, String modelName)
-  {
-    if (af == null)
-    {
-      af = getCurrentAlignFrame();
-    }
-    return CalculationChooser.openPcaPanel(af, modelName, null);
-  }
-
-  @Override
-  public String getSelectedSequencesAsAlignment(String format,
-          boolean suffix)
-  {
-    return getSelectedSequencesAsAlignmentFrom(null, format, suffix);
-  }
-
-  @Override
-  public String getSelectedSequencesAsAlignmentFrom(AlignFrame alf,
-          String format, boolean suffix)
-  {
-    if (alf == null)
-    {
-      alf = getCurrentAlignFrame();
-    }
-    return appLoader.getSelectedSequencesAsAlignmentFrom(alf, format,
-            "" + suffix);
-  }
-
-  @Override
-  public String arrayToSeparatorList(String[] array)
-  {
-    return appLoader.arrayToSeparatorList(array);
-  }
-
-  @Override
-  public String[] separatorListToArray(String list)
-  {
-    return appLoader.separatorListToArray(list);
-  }
-
-  //// probably not needed in JalviewJS -- From when Jmol and Jalview did not
-  //// have a direct connection?
-
-  @Override
-  public void setMouseoverListener(String listener)
-  {
-    // TODO Auto-generated method stub
-
-  }
-
-  @Override
-  public void setMouseoverListener(AlignFrame af, String listener)
-  {
-    // TODO Auto-generated method stub
-
-  }
-
-  @Override
-  public void setSelectionListener(String listener)
-  {
-    // TODO Auto-generated method stub
-
-  }
-
-  @Override
-  public void setSelectionListener(AlignFrame af, String listener)
-  {
-    // TODO Auto-generated method stub
-
-  }
-
-  @Override
-  public void setStructureListener(String listener, String modelSet)
-  {
-    // TODO Auto-generated method stub
-
-  }
-
-  @Override
-  public void removeJavascriptListener(AlignFrame af, String listener)
+  
+  public void notifyWorker(AlignCalcWorkerI worker, String status)
   {
-    // TODO Auto-generated method stub
-
+    // System.out.println("Jalview worker " + worker.getClass().getSimpleName()
+    // + " " + status);
   }
 
-  @Override
-  public void mouseOverStructure(String pdbResNum, String chain,
-          String pdbfile)
-  {
-    // TODO Auto-generated method stub
 
-  }
+  private static boolean isInteractive = true;
 
-  @Override
-  public void showOverview()
+  public static boolean isInteractive()
   {
-    currentAlignFrame.overviewMenuItem_actionPerformed(null);
+    return isInteractive;
   }
 
-  public void notifyWorker(AlignCalcWorkerI worker, String status)
+  public static void setInteractive(boolean tf)
   {
-    // System.out.println("Jalview worker " + worker.getClass().getSimpleName()
-    // + " " + status);
+    isInteractive = tf;
   }
 
 }
diff --git a/src/jalview/bin/JalviewAppLoader.java b/src/jalview/bin/JalviewAppLoader.java
deleted file mode 100644 (file)
index 1cdeaec..0000000
+++ /dev/null
@@ -1,1500 +0,0 @@
-package jalview.bin;
-
-import jalview.api.JalviewApp;
-import jalview.api.StructureSelectionManagerProvider;
-import jalview.datamodel.Alignment;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.AlignmentOrder;
-import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.PDBEntry;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.gui.AlignFrame;
-import jalview.gui.AlignViewport;
-import jalview.gui.Desktop;
-import jalview.io.AnnotationFile;
-import jalview.io.AppletFormatAdapter;
-import jalview.io.DataSourceType;
-import jalview.io.FeaturesFile;
-import jalview.io.FileFormat;
-import jalview.io.FileFormatI;
-import jalview.io.FileFormats;
-import jalview.io.IdentifyFile;
-import jalview.io.JPredFile;
-import jalview.io.JnetAnnotationMaker;
-import jalview.io.NewickFile;
-import jalview.structure.SelectionSource;
-import jalview.structure.StructureSelectionManager;
-import jalview.util.HttpUtils;
-import jalview.util.MessageManager;
-
-import java.awt.EventQueue;
-import java.io.IOException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
-import java.util.Vector;
-
-/**
- * A class to load parameters for either JalviewLite or Jalview
- * 
- * @author hansonr
- *
- */
-public class JalviewAppLoader
-{
-
-  private JalviewApp app; // Jalview or JalviewJS or JalviewLite
-
-  private boolean debug;
-
-  String separator = "\u00AC"; // JalviewLite note: the default used to
-                                       // be '|', but many sequence IDS include
-                                       // pipes.
-
-  public String getSeparator()
-  {
-    return separator;
-  }
-
-  public void setSeparator(String separator)
-  {
-    this.separator = separator;
-  }
-
-  public JalviewAppLoader(boolean debug)
-  {
-    this.debug = debug;
-  }
-
-  public void load(JalviewApp app)
-  {
-
-    this.app = app;
-
-    String sep = app.getParameter("separator");
-    if (sep != null)
-    {
-      if (sep.length() > 0)
-      {
-        separator = sep;
-      }
-      else
-      {
-        throw new Error(MessageManager
-                .getString("error.invalid_separator_parameter"));
-      }
-    }
-
-    loadTree();
-    loadScoreFile();
-    loadFeatures();
-    loadAnnotations();
-    loadJnetFile();
-    loadPdbFiles();
-    callInitCallback();
-  }
-
-  /**
-   * Load PDBFiles if any specified by parameter(s). Returns true if loaded,
-   * else false.
-   * 
-   * @param loaderFrame
-   * @return
-   */
-  protected boolean loadPdbFiles()
-  {
-    boolean result = false;
-    /*
-     * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6 -
-     * related to JAL-434
-     */
-
-    boolean doAlign = app.getDefaultParameter("alignpdbfiles", false);
-    app.setAlignPdbStructures(doAlign);
-    /*
-     * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
-     * PDB|1GAQ|1GAQ|C">
-     * 
-     * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
-     * 
-     * <param name="PDBfile3" value="1q0o Q45135_9MICO">
-     */
-
-    // Accumulate pdbs here if they are heading for the same view (if
-    // alignPdbStructures is true)
-    Vector<Object[]> pdbs = new Vector<>();
-    // create a lazy matcher if we're asked to
-    jalview.analysis.SequenceIdMatcher matcher = (app
-            .getDefaultParameter("relaxedidmatch", false))
-                    ? new jalview.analysis.SequenceIdMatcher(
-                            app.getViewport().getAlignment()
-                                    .getSequencesArray())
-                    : null;
-
-    int pdbFileCount = 0;
-    String param;
-    do
-    {
-      if (pdbFileCount > 0)
-      {
-        param = app.getParameter("PDBFILE" + pdbFileCount);
-      }
-      else
-      {
-        param = app.getParameter("PDBFILE");
-      }
-
-      if (param != null)
-      {
-        PDBEntry pdb = new PDBEntry();
-
-        String seqstring;
-        SequenceI[] seqs = null;
-        String[] chains = null;
-
-        StringTokenizer st = new StringTokenizer(param, " ");
-
-        if (st.countTokens() < 2)
-        {
-          String sequence = app.getParameter("PDBSEQ");
-          if (sequence != null)
-          {
-            seqs = new SequenceI[] { matcher == null
-                    ? (Sequence) app.getViewport().getAlignment()
-                            .findName(sequence)
-                    : matcher.findIdMatch(sequence) };
-          }
-
-        }
-        else
-        {
-          param = st.nextToken();
-          List<SequenceI> tmp = new ArrayList<>();
-          List<String> tmp2 = new ArrayList<>();
-
-          while (st.hasMoreTokens())
-          {
-            seqstring = st.nextToken();
-            StringTokenizer st2 = new StringTokenizer(seqstring, "=");
-            if (st2.countTokens() > 1)
-            {
-              // This is the chain
-              tmp2.add(st2.nextToken());
-              seqstring = st2.nextToken();
-            }
-            tmp.add(matcher == null
-                    ? (Sequence) app.getViewport().getAlignment()
-                            .findName(seqstring)
-                    : matcher.findIdMatch(seqstring));
-          }
-
-          seqs = tmp.toArray(new SequenceI[tmp.size()]);
-          if (tmp2.size() == tmp.size())
-          {
-            chains = tmp2.toArray(new String[tmp2.size()]);
-          }
-        }
-        pdb.setId(param);
-        ret[0] = param;
-        DataSourceType protocol = resolveFileProtocol(app, ret);
-        // TODO check JAL-357 for files in a jar (CLASSLOADER)
-        pdb.setFile(ret[0]);
-
-        if (seqs != null)
-        {
-          for (int i = 0; i < seqs.length; i++)
-          {
-            if (seqs[i] != null)
-            {
-              ((Sequence) seqs[i]).addPDBId(pdb);
-              StructureSelectionManager
-                      .getStructureSelectionManager(
-                              (StructureSelectionManagerProvider) app)
-                      .registerPDBEntry(pdb);
-            }
-            else
-            {
-              if (debug)
-              {
-                // this may not really be a problem but we give a warning
-                // anyway
-                System.err.println(
-                        "Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "
-                                + i + ")");
-              }
-            }
-          }
-
-          if (doAlign)
-          {
-            pdbs.addElement(new Object[] { pdb, seqs, chains, protocol });
-          }
-          else
-          {
-            app.newStructureView(pdb, seqs, chains, protocol);
-          }
-        }
-      }
-
-      pdbFileCount++;
-    } while (param != null || pdbFileCount < 10);
-    if (pdbs.size() > 0)
-    {
-      SequenceI[][] seqs = new SequenceI[pdbs.size()][];
-      PDBEntry[] pdb = new PDBEntry[pdbs.size()];
-      String[][] chains = new String[pdbs.size()][];
-      String[] protocols = new String[pdbs.size()];
-      for (int pdbsi = 0, pdbsiSize = pdbs
-              .size(); pdbsi < pdbsiSize; pdbsi++)
-      {
-        Object[] o = pdbs.elementAt(pdbsi);
-        pdb[pdbsi] = (PDBEntry) o[0];
-        seqs[pdbsi] = (SequenceI[]) o[1];
-        chains[pdbsi] = (String[]) o[2];
-        protocols[pdbsi] = (String) o[3];
-      }
-      app.alignedStructureView(pdb, seqs, chains, protocols);
-      result = true;
-    }
-    return result;
-  }
-
-  /**
-   * Load in a Jnetfile if specified by parameter. Returns true if loaded, else
-   * false.
-   * 
-   * @param alignFrame
-   * @return
-   */
-  protected boolean loadJnetFile()
-  {
-    boolean result = false;
-    String param = app.getParameter("jnetfile");
-    if (param == null)
-    {
-      // jnet became jpred around 2016
-      param = app.getParameter("jpredfile");
-    }
-    if (param != null)
-    {
-      try
-      {
-        ret[0] = param;
-        DataSourceType protocol = resolveFileProtocol(app, ret);
-        JPredFile predictions = new JPredFile(ret[0], protocol);
-        JnetAnnotationMaker.add_annotation(predictions,
-                app.getViewport().getAlignment(), 0, false);
-        // false == do not add sequence profile from concise output
-        app.getViewport().getAlignment().setupJPredAlignment();
-        app.updateForAnnotations();
-        result = true;
-      } catch (Exception ex)
-      {
-        ex.printStackTrace();
-      }
-    }
-    return result;
-  }
-
-  /**
-   * Load annotations if specified by parameter. Returns true if loaded, else
-   * false.
-   * 
-   * @param alignFrame
-   * @return
-   */
-  protected boolean loadAnnotations()
-  {
-    boolean result = false;
-    String param = app.getParameter("annotations");
-    if (param != null)
-    {
-      ret[0] = param;
-      DataSourceType protocol = resolveFileProtocol(app, ret);
-      param = ret[0];
-      if (new AnnotationFile().annotateAlignmentView(app.getViewport(),
-              param, protocol))
-      {
-        app.updateForAnnotations();
-        result = true;
-      }
-      else
-      {
-        System.err
-                .println("Annotations were not added from annotation file '"
-                        + param + "'");
-      }
-    }
-    return result;
-  }
-
-  /**
-   * Load features file and view settings as specified by parameters. Returns
-   * true if features were loaded, else false.
-   * 
-   * @param alignFrame
-   * @return
-   */
-  protected boolean loadFeatures()
-  {
-    boolean result = false;
-    // ///////////////////////////
-    // modify display of features
-    // we do this before any features have been loaded, ensuring any hidden
-    // groups are hidden when features first displayed
-    //
-    // hide specific groups
-    //
-    String param = app.getParameter("hidefeaturegroups");
-    if (param != null)
-    {
-      app.setFeatureGroupState(separatorListToArray(param, separator),
-              false);
-      // app.setFeatureGroupStateOn(newAlignFrame, param, false);
-    }
-    // show specific groups
-    param = app.getParameter("showfeaturegroups");
-    if (param != null)
-    {
-      app.setFeatureGroupState(separatorListToArray(param, separator),
-              true);
-      // app.setFeatureGroupStateOn(newAlignFrame, param, true);
-    }
-    // and now load features
-    param = app.getParameter("features");
-    if (param != null)
-    {
-      ret[0] = param;
-      DataSourceType protocol = resolveFileProtocol(app, ret);
-
-      result = app.parseFeaturesFile(ret[0], protocol);
-    }
-
-    param = app.getParameter("showFeatureSettings");
-    if (param != null && param.equalsIgnoreCase("true"))
-    {
-      app.newFeatureSettings();
-    }
-    return result;
-  }
-
-  /**
-   * Load a score file if specified by parameter. Returns true if file was
-   * loaded, else false.
-   * 
-   * @param loaderFrame
-   */
-  protected boolean loadScoreFile()
-  {
-    boolean result = false;
-    String sScoreFile = app.getParameter("scoreFile");
-    if (sScoreFile != null && !"".equals(sScoreFile))
-    {
-      try
-      {
-        if (debug)
-        {
-          System.err.println(
-                  "Attempting to load T-COFFEE score file from the scoreFile parameter");
-        }
-        result = app.loadScoreFile(sScoreFile);
-        if (!result)
-        {
-          System.err.println(
-                  "Failed to parse T-COFFEE parameter as a valid score file ('"
-                          + sScoreFile + "')");
-        }
-      } catch (Exception e)
-      {
-        System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
-                sScoreFile, e.getMessage());
-      }
-    }
-    return result;
-  }
-
-  String[] ret = new String[1];
-
-  /**
-   * Load a tree for the alignment if specified by parameter. Returns true if a
-   * tree was loaded, else false.
-   * 
-   * @param loaderFrame
-   * @return
-   */
-  protected boolean loadTree()
-  {
-    boolean result = false;
-    String treeFile = app.getParameter("tree");
-    if (treeFile == null)
-    {
-      treeFile = app.getParameter("treeFile");
-    }
-
-    if (treeFile != null)
-    {
-      try
-      {
-        ret[0] = treeFile;
-        NewickFile fin = new NewickFile(treeFile,
-                resolveFileProtocol(app, ret));
-        fin.parse();
-
-        if (fin.getTree() != null)
-        {
-          app.loadTree(fin, ret[0]);
-          result = true;
-          if (debug)
-          {
-            System.out.println("Successfully imported tree.");
-          }
-        }
-        else
-        {
-          if (debug)
-          {
-            System.out.println(
-                    "Tree parameter did not resolve to a valid tree.");
-          }
-        }
-      } catch (Exception ex)
-      {
-        ex.printStackTrace();
-      }
-    }
-    return result;
-  }
-
-  /**
-   * form a complete URL given a path to a resource and a reference location on
-   * the same server
-   * 
-   * @param targetPath
-   *          - an absolute path on the same server as localref or a document
-   *          located relative to localref
-   * @param localref
-   *          - a URL on the same server as url
-   * @return a complete URL for the resource located by url
-   */
-  public static String resolveUrlForLocalOrAbsolute(String targetPath,
-          URL localref)
-  {
-    String resolvedPath = "";
-    if (targetPath.startsWith("/"))
-    {
-      String codebase = localref.toString();
-      String localfile = localref.getFile();
-      resolvedPath = codebase.substring(0,
-              codebase.length() - localfile.length()) + targetPath;
-      return resolvedPath;
-    }
-
-    /*
-     * get URL path and strip off any trailing file e.g.
-     * www.jalview.org/examples/index.html#applets?a=b is trimmed to
-     * www.jalview.org/examples/
-     */
-    String urlPath = localref.toString();
-    String directoryPath = urlPath;
-    int lastSeparator = directoryPath.lastIndexOf("/");
-    if (lastSeparator > 0)
-    {
-      directoryPath = directoryPath.substring(0, lastSeparator + 1);
-    }
-
-    if (targetPath.startsWith("/"))
-    {
-      /*
-       * construct absolute URL to a file on the server - this is not allowed?
-       */
-      // String localfile = localref.getFile();
-      // resolvedPath = urlPath.substring(0,
-      // urlPath.length() - localfile.length())
-      // + targetPath;
-      resolvedPath = directoryPath + targetPath.substring(1);
-    }
-    else
-    {
-      resolvedPath = directoryPath + targetPath;
-    }
-    // if (debug)
-    // {
-    // System.err.println(
-    // "resolveUrlForLocalOrAbsolute returning " + resolvedPath);
-    // }
-    return resolvedPath;
-  }
-
-  /**
-   * parse the string into a list
-   * 
-   * @param list
-   * @param separator
-   * @return elements separated by separator
-   */
-  public static String[] separatorListToArray(String list, String separator)
-  {
-    // TODO use StringUtils version (slightly different...)
-    int seplen = separator.length();
-    if (list == null || list.equals("") || list.equals(separator))
-    {
-      return null;
-    }
-    Vector<String> jv = new Vector<>();
-    int cp = 0, pos;
-    while ((pos = list.indexOf(separator, cp)) > cp)
-    {
-      jv.addElement(list.substring(cp, pos));
-      cp = pos + seplen;
-    }
-    if (cp < list.length())
-    {
-      String c = list.substring(cp);
-      if (!c.equals(separator))
-      {
-        jv.addElement(c);
-      }
-    }
-    if (jv.size() > 0)
-    {
-      String[] v = new String[jv.size()];
-      for (int i = 0; i < v.length; i++)
-      {
-        v[i] = jv.elementAt(i);
-      }
-      jv.removeAllElements();
-      // if (debug)
-      // {
-      // System.err.println("Array from '" + separator
-      // + "' separated List:\n" + v.length);
-      // for (int i = 0; i < v.length; i++)
-      // {
-      // System.err.println("item " + i + " '" + v[i] + "'");
-      // }
-      // }
-      return v;
-    }
-    // if (debug)
-    // {
-    // System.err.println(
-    // "Empty Array from '" + separator + "' separated List");
-    // }
-    return null;
-  }
-
-  public static DataSourceType resolveFileProtocol(JalviewApp app,
-          String[] retPath)
-  {
-    String path = retPath[0];
-    /*
-     * is it paste data?
-     */
-    if (path.startsWith("PASTE"))
-    {
-      retPath[0] = path.substring(5);
-      return DataSourceType.PASTE;
-    }
-
-    /*
-     * is it a URL?
-     */
-    if (path.indexOf("://") >= 0)
-    {
-      return DataSourceType.URL;
-    }
-
-    /*
-     * try relative to document root
-     */
-    URL documentBase = app.getDocumentBase();
-    String withDocBase = resolveUrlForLocalOrAbsolute(path, documentBase);
-    if (HttpUtils.isValidUrl(withDocBase))
-    {
-      // if (debug)
-      // {
-      // System.err.println("Prepended document base '" + documentBase
-      // + "' to make: '" + withDocBase + "'");
-      // }
-      retPath[0] = withDocBase;
-      return DataSourceType.URL;
-    }
-
-    /*
-     * try relative to codebase (if different to document base)
-     */
-    URL codeBase = app.getCodeBase();
-    String withCodeBase = resolveUrlForLocalOrAbsolute(path, codeBase);
-    if (!withCodeBase.equals(withDocBase)
-            && HttpUtils.isValidUrl(withCodeBase))
-    {
-      // if (debug)
-      // {
-      // System.err.println("Prepended codebase '" + codeBase
-      // + "' to make: '" + withCodeBase + "'");
-      // }
-      retPath[0] = withCodeBase;
-      return DataSourceType.URL;
-    }
-
-    /*
-     * try locating by classloader; try this last so files in the directory
-     * are resolved using document base
-     */
-    if (inArchive(app.getClass(), path))
-    {
-      return DataSourceType.CLASSLOADER;
-    }
-    return null;
-  }
-
-  /**
-   * Discovers whether the given file is in the Applet Archive
-   * 
-   * @param f
-   *          String
-   * @return boolean
-   */
-  private static boolean inArchive(Class<?> c, String f)
-  {
-    // This might throw a security exception in certain browsers
-    // Netscape Communicator for instance.
-    try
-    {
-      boolean rtn = (c.getResourceAsStream("/" + f) != null);
-      // if (debug)
-      // {
-      // System.err.println("Resource '" + f + "' was "
-      // + (rtn ? "" : "not ") + "located by classloader.");
-      // }
-      return rtn;
-    } catch (Exception ex)
-    {
-      System.out.println("Exception checking resources: " + f + " " + ex);
-      return false;
-    }
-  }
-
-  public void callInitCallback()
-  {
-    String initjscallback = app.getParameter("oninit");
-    if (initjscallback == null)
-    {
-      return;
-    }
-    initjscallback = initjscallback.trim();
-    if (initjscallback.length() > 0)
-    {
-      // TODO
-    }
-  }
-
-  /**
-   * read sequence1...sequenceN as a raw alignment
-   * 
-   * @param jalviewApp
-   * @return
-   */
-  public String getPastedSequence(JalviewApp jalviewApp)
-  {
-    StringBuffer data = new StringBuffer("PASTE");
-    int i = 1;
-    String file = null;
-    while ((file = app.getParameter("sequence" + i)) != null)
-    {
-      data.append(file.toString() + "\n");
-      i++;
-    }
-    if (data.length() > 5)
-    {
-      file = data.toString();
-    }
-    return file;
-  }
-
-  /**
-   * concatenate the list with separator
-   * 
-   * @param list
-   * @param separator
-   * @return concatenated string
-   */
-  public static String arrayToSeparatorList(String[] list, String separator)
-  {
-    // TODO use StringUtils version
-    StringBuffer v = new StringBuffer();
-    if (list != null && list.length > 0)
-    {
-      for (int i = 0, iSize = list.length; i < iSize; i++)
-      {
-        if (list[i] != null)
-        {
-          if (i > 0)
-          {
-            v.append(separator);
-          }
-          v.append(list[i]);
-        }
-      }
-      // if (debug)
-      // {
-      // System.err
-      // .println("Returning '" + separator + "' separated List:\n");
-      // System.err.println(v);
-      // }
-      return v.toString();
-    }
-    // if (debug)
-    // {
-    // System.err.println(
-    // "Returning empty '" + separator + "' separated List\n");
-    // }
-    return "" + separator;
-  }
-
-  public String arrayToSeparatorList(String[] array)
-  {
-    return arrayToSeparatorList(array, separator);
-  }
-
-  public String getSelectedSequencesFrom(AlignFrame alf, String sep)
-  {
-    StringBuffer result = new StringBuffer("");
-    if (sep == null || sep.length() == 0)
-    {
-      sep = separator; // "+0x00AC;
-    }
-    AlignViewport v = alf.getViewport();
-    if (v.getSelectionGroup() != null)
-    {
-      SequenceI[] seqs = v.getSelectionGroup()
-              .getSequencesInOrder(v.getAlignment());
-
-      for (int i = 0; i < seqs.length; i++)
-      {
-        result.append(seqs[i].getName());
-        result.append(sep);
-      }
-    }
-
-    return result.toString();
-  }
-
-  public void setFeatureGroupStateOn(final AlignFrame alf,
-          final String groups, boolean state)
-  {
-    java.awt.EventQueue.invokeLater(new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        alf.setFeatureGroupState(
-                separatorListToArray(groups, separator), state);
-      }
-    });
-  }
-
-  public String getFeatureGroupsOfStateOn(AlignFrame alf, boolean visible)
-  {
-    return arrayToSeparatorList(
-            alf.getFeatureGroupsOfState(visible));
-  }
-
-  public void scrollViewToIn(final AlignFrame alf, final String topRow,
-          final String leftHandColumn)
-  {
-    // TODO test
-    java.awt.EventQueue.invokeLater(new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        try
-        {
-          alf.scrollTo(Integer.valueOf(topRow).intValue(),
-                  Integer.valueOf(leftHandColumn).intValue());
-
-        } catch (Exception ex)
-        {
-          System.err.println("Couldn't parse integer arguments (topRow='"
-                  + topRow + "' and leftHandColumn='" + leftHandColumn
-                  + "')");
-          ex.printStackTrace();
-        }
-      }
-    });
-  }
-
-  public void scrollViewToRowIn(final AlignFrame alf, final String topRow)
-  {
-    // TODO test
-
-    java.awt.EventQueue.invokeLater(new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        try
-        {
-          alf.scrollToRow(Integer.valueOf(topRow).intValue());
-
-        } catch (Exception ex)
-        {
-          System.err.println("Couldn't parse integer arguments (topRow='"
-                  + topRow + "')");
-          ex.printStackTrace();
-        }
-
-      }
-    });
-  }
-
-  public void scrollViewToColumnIn(final AlignFrame alf,
-          final String leftHandColumn)
-  {
-    // TODO test
-    java.awt.EventQueue.invokeLater(new Runnable()
-    {
-
-      @Override
-      public void run()
-      {
-        try
-        {
-          alf
-                  .scrollToColumn(Integer.valueOf(leftHandColumn).intValue());
-
-        } catch (Exception ex)
-        {
-          System.err.println(
-                  "Couldn't parse integer arguments (leftHandColumn='"
-                          + leftHandColumn + "')");
-          ex.printStackTrace();
-        }
-      }
-    });
-
-  }
-
-  public boolean addPdbFile(AlignFrame alf, String sequenceId,
-          String pdbEntryString, String pdbFile)
-  {
-    AlignFrame alFrame = alf;
-    SequenceI toaddpdb = alFrame.getViewport().getAlignment()
-            .findName(sequenceId);
-    boolean needtoadd = false;
-    if (toaddpdb != null)
-    {
-      Vector<PDBEntry> pdbe = toaddpdb.getAllPDBEntries();
-      PDBEntry pdbentry = null;
-      if (pdbe != null && pdbe.size() > 0)
-      {
-        for (int pe = 0, peSize = pdbe.size(); pe < peSize; pe++)
-        {
-          pdbentry = pdbe.elementAt(pe);
-          if (!pdbentry.getId().equals(pdbEntryString)
-                  && !pdbentry.getFile().equals(pdbFile))
-          {
-            pdbentry = null;
-          }
-          else
-          {
-            continue;
-          }
-        }
-      }
-      if (pdbentry == null)
-      {
-        pdbentry = new PDBEntry();
-        pdbentry.setId(pdbEntryString);
-        pdbentry.setFile(pdbFile);
-        needtoadd = true; // add this new entry to sequence.
-      }
-      // resolve data source
-      // TODO: this code should be a refactored to an io package
-      DataSourceType protocol = AppletFormatAdapter.resolveProtocol(pdbFile,
-              FileFormat.PDB);
-      if (protocol == null)
-      {
-        return false;
-      }
-      if (needtoadd)
-      {
-        pdbentry.setProperty("protocol", protocol);
-        toaddpdb.addPDBId(pdbentry);
-        alFrame.alignPanel.getStructureSelectionManager()
-                .registerPDBEntry(pdbentry);
-      }
-    }
-    return true;
-  }
-
-  public AlignFrame loadAlignment(String text, int width, int height,
-          String title)
-  {
-    AlignmentI al = null;
-
-    try
-    {
-      FileFormatI format = new IdentifyFile().identify(text,
-              DataSourceType.PASTE);
-      al = new AppletFormatAdapter().readFile(text, DataSourceType.PASTE,
-              format);
-      if (al.getHeight() > 0)
-      {
-        return new AlignFrame(al, width, height, title);
-      }
-    } catch (IOException ex)
-    {
-      ex.printStackTrace();
-    }
-    return null;
-  }
-
-  public String getFeatureGroupsOn(AlignFrame alf)
-  {
-    return arrayToSeparatorList(
-            alf.getFeatureGroups());
-  }
-
-  public void highlightIn(final AlignFrame alf, final String sequenceId,
-          final String position, final String alignedPosition)
-  {
-    // TODO: could try to highlight in all alignments if alf==null
-    jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
-            alf.getViewport().getAlignment()
-                    .getSequencesArray());
-    final SequenceI sq = matcher.findIdMatch(sequenceId);
-    if (sq != null)
-    {
-      int apos = -1;
-      try
-      {
-        apos = Integer.valueOf(position).intValue();
-        apos--;
-      } catch (NumberFormatException ex)
-      {
-        return;
-      }
-      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))
-      {
-        java.awt.EventQueue.invokeLater(new Runnable()
-        {
-          @Override
-          public void run()
-          {
-            StructureSelectionManager
-                    .getStructureSelectionManager(Desktop.getInstance())
-                    .mouseOverVamsasSequence(sq, sq.findIndex(pos), null);
-          }
-        });
-      }
-      else
-      {
-        java.awt.EventQueue.invokeLater(new Runnable()
-        {
-          @Override
-          public void run()
-          {
-            StructureSelectionManager
-                    .getStructureSelectionManager(Desktop.getInstance())
-                    .mouseOverVamsasSequence(sq, pos, null);
-          }
-        });
-      }
-    }
-  }
-
-  public void selectIn(final AlignFrame alf, String sequenceIds,
-          String columns, String sep)
-  {
-    if (sep == null || sep.length() == 0)
-    {
-      sep = separator;
-    }
-    else
-    {
-      if (debug)
-      {
-        System.err.println("Selecting region using separator string '"
-                + separator + "'");
-      }
-    }
-    // deparse fields
-    String[] ids = JalviewAppLoader.separatorListToArray(sequenceIds, sep);
-    String[] cols = JalviewAppLoader.separatorListToArray(columns, sep);
-    final SequenceGroup sel = new SequenceGroup();
-    final ColumnSelection csel = new ColumnSelection();
-    AlignmentI al = alf.getViewport().getAlignment();
-    jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
-            alf.getViewport().getAlignment()
-                    .getSequencesArray());
-    int start = 0, end = al.getWidth(), alw = al.getWidth();
-    boolean seqsfound = true;
-    if (ids != null && ids.length > 0)
-    {
-      seqsfound = false;
-      for (int i = 0; i < ids.length; i++)
-      {
-        if (ids[i].trim().length() == 0)
-        {
-          continue;
-        }
-        SequenceI sq = matcher.findIdMatch(ids[i]);
-        if (sq != null)
-        {
-          seqsfound = true;
-          sel.addSequence(sq, false);
-        }
-      }
-    }
-    boolean inseqpos = false;
-    if (cols != null && cols.length > 0)
-    {
-      boolean seset = false;
-      for (int i = 0; i < cols.length; i++)
-      {
-        String cl = cols[i].trim();
-        if (cl.length() == 0)
-        {
-          continue;
-        }
-        int p;
-        if ((p = cl.indexOf("-")) > -1)
-        {
-          int from = -1, to = -1;
-          try
-          {
-            from = Integer.valueOf(cl.substring(0, p)).intValue();
-            from--;
-          } catch (NumberFormatException ex)
-          {
-            System.err.println(
-                    "ERROR: Couldn't parse first integer in range element column selection string '"
-                            + cl + "' - format is 'from-to'");
-            return;
-          }
-          try
-          {
-            to = Integer.valueOf(cl.substring(p + 1)).intValue();
-            to--;
-          } catch (NumberFormatException ex)
-          {
-            System.err.println(
-                    "ERROR: Couldn't parse second integer in range element column selection string '"
-                            + cl + "' - format is 'from-to'");
-            return;
-          }
-          if (from >= 0 && to >= 0)
-          {
-            // valid range
-            if (from < to)
-            {
-              int t = to;
-              to = from;
-              to = t;
-            }
-            if (!seset)
-            {
-              start = from;
-              end = to;
-              seset = true;
-            }
-            else
-            {
-              // comment to prevent range extension
-              if (start > from)
-              {
-                start = from;
-              }
-              if (end < to)
-              {
-                end = to;
-              }
-            }
-            for (int r = from; r <= to; r++)
-            {
-              if (r >= 0 && r < alw)
-              {
-                csel.addElement(r);
-              }
-            }
-            if (debug)
-            {
-              System.err.println("Range '" + cl + "' deparsed as [" + from
-                      + "," + to + "]");
-            }
-          }
-          else
-          {
-            System.err.println("ERROR: Invalid Range '" + cl
-                    + "' deparsed as [" + from + "," + to + "]");
-          }
-        }
-        else
-        {
-          int r = -1;
-          try
-          {
-            r = Integer.valueOf(cl).intValue();
-            r--;
-          } catch (NumberFormatException ex)
-          {
-            if (cl.toLowerCase().equals("sequence"))
-            {
-              // we are in the dataset sequence's coordinate frame.
-              inseqpos = true;
-            }
-            else
-            {
-              System.err.println(
-                      "ERROR: Couldn't parse integer from point selection element of column selection string '"
-                              + cl + "'");
-              return;
-            }
-          }
-          if (r >= 0 && r <= alw)
-          {
-            if (!seset)
-            {
-              start = r;
-              end = r;
-              seset = true;
-            }
-            else
-            {
-              // comment to prevent range extension
-              if (start > r)
-              {
-                start = r;
-              }
-              if (end < r)
-              {
-                end = r;
-              }
-            }
-            csel.addElement(r);
-            if (debug)
-            {
-              System.err.println("Point selection '" + cl
-                      + "' deparsed as [" + r + "]");
-            }
-          }
-          else
-          {
-            System.err.println("ERROR: Invalid Point selection '" + cl
-                    + "' deparsed as [" + r + "]");
-          }
-        }
-      }
-    }
-    if (seqsfound)
-    {
-      // we only propagate the selection when it was the null selection, or the
-      // given sequences were found in the alignment.
-      if (inseqpos && sel.getSize() > 0)
-      {
-        // assume first sequence provides reference frame ?
-        SequenceI rs = sel.getSequenceAt(0);
-        start = rs.findIndex(start);
-        end = rs.findIndex(end);
-        List<Integer> cs = new ArrayList<>(csel.getSelected());
-        csel.clear();
-        for (Integer selectedCol : cs)
-        {
-          csel.addElement(rs.findIndex(selectedCol));
-        }
-      }
-      sel.setStartRes(start);
-      sel.setEndRes(end);
-      EventQueue.invokeLater(new Runnable()
-      {
-        @Override
-        public void run()
-        {
-          alf.select(sel, csel, alf
-                  .getCurrentView().getAlignment().getHiddenColumns());
-        }
-      });
-    }
-  }
-
-  public String getAlignmentOrderFrom(AlignFrame alf, String sep)
-  {
-    AlignmentI alorder = alf.getViewport().getAlignment();
-    String[] order = new String[alorder.getHeight()];
-    for (int i = 0; i < order.length; i++)
-    {
-      order[i] = alorder.getSequenceAt(i).getName();
-    }
-    return arrayToSeparatorList(order, sep);
-  }
-
-  public String getSelectedSequencesAsAlignmentFrom(AlignFrame alf,
-          String format, String suffix)
-  {
-    try
-    {
-      AlignViewport vp = alf.getViewport();
-      FileFormatI theFormat = FileFormats.getInstance().forName(format);
-      boolean seqlimits = (suffix == null
-              || suffix.equalsIgnoreCase("true"));
-      if (vp.getSelectionGroup() != null)
-      {
-        // JBPNote: getSelectionAsNewSequence behaviour has changed - this
-        // method now returns a full copy of sequence data
-        // TODO consider using getSequenceSelection instead here
-        String reply = new AppletFormatAdapter().formatSequences(theFormat,
-                new Alignment(vp.getSelectionAsNewSequence()),
-                seqlimits);
-        return reply;
-      }
-    } catch (IllegalArgumentException ex)
-    {
-      ex.printStackTrace();
-      return "Error retrieving alignment, possibly invalid format specifier: "
-              + format;
-    }
-    return "";
-  }
-
-  public String orderAlignmentBy(AlignFrame alf, String order,
-          String undoName, String sep)
-  {
-    if (sep == null || sep.length() == 0)
-    {
-      sep = separator;
-    }
-    String[] ids = JalviewAppLoader.separatorListToArray(order, sep);
-    SequenceI[] sqs = null;
-    if (ids != null && ids.length > 0)
-    {
-      jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
-              alf.getViewport().getAlignment()
-                      .getSequencesArray());
-      int s = 0;
-      sqs = new SequenceI[ids.length];
-      for (int i = 0; i < ids.length; i++)
-      {
-        if (ids[i].trim().length() == 0)
-        {
-          continue;
-        }
-        SequenceI sq = matcher.findIdMatch(ids[i]);
-        if (sq != null)
-        {
-          sqs[s++] = sq;
-        }
-      }
-      if (s > 0)
-      {
-        SequenceI[] sqq = new SequenceI[s];
-        System.arraycopy(sqs, 0, sqq, 0, s);
-        sqs = sqq;
-      }
-      else
-      {
-        sqs = null;
-      }
-    }
-    if (sqs == null)
-    {
-      return "";
-    }
-    ;
-    final AlignmentOrder aorder = new AlignmentOrder(sqs);
-
-    if (undoName != null && undoName.trim().length() == 0)
-    {
-      undoName = null;
-    }
-    final String _undoName = undoName;
-    // TODO: deal with synchronization here: cannot raise any events until after
-    // this has returned.
-    return alf.sortBy(aorder, _undoName) ? "true" : "";
-  }
-
-  public String getAlignmentFrom(AlignFrame alf, String format,
-          String suffix)
-  {
-    try
-    {
-      boolean seqlimits = (suffix == null
-              || suffix.equalsIgnoreCase("true"));
-
-      FileFormatI theFormat = FileFormats.getInstance().forName(format);
-      String reply = new AppletFormatAdapter().formatSequences(theFormat,
-              alf.getViewport().getAlignment(), seqlimits);
-      return reply;
-    } catch (IllegalArgumentException ex)
-    {
-      ex.printStackTrace();
-      return "Error retrieving alignment, possibly invalid format specifier: "
-              + format;
-    }
-  }
-
-  public void loadAnnotationFrom(AlignFrame alf, String annotation)
-  {
-    if (new AnnotationFile().annotateAlignmentView(
-            alf.getViewport(), annotation,
-            DataSourceType.PASTE))
-    {
-      alf.alignPanel.fontChanged();
-      alf.alignPanel.setScrollValues(0, 0);
-    }
-    else
-    {
-      alf.parseFeaturesFile(annotation,
-              DataSourceType.PASTE);
-    }
-  }
-
-  public boolean loadFeaturesFrom(AlignFrame alf, String features,
-          boolean autoenabledisplay)
-  {
-    boolean ret = alf.parseFeaturesFile(features,
-            DataSourceType.PASTE);
-    if (!ret)
-    {
-      return false;
-    }
-    if (autoenabledisplay)
-    {
-      alf.getViewport().setShowSequenceFeatures(true);
-      // this next was for a checkbox in JalviewLite
-      // ((AlignFrame) alf).getViewport().sequenceFeatures.setState(true);
-    }
-    return true;
-  }
-
-  /**
-   * JavaScript interface to print the alignment frame
-   * 
-   * @param alf
-   * @param format
-   *          "jalview" or "gff" with or without ";includeComplement" or
-   *          ";includeNonpositional"; default with no ";" is
-   *          ";includeNonpositional"
-   * @return
-   */
-  public String getFeaturesFrom(AlignFrame alf, String format)
-  {
-    AlignFrame f = (alf);
-
-    String features;
-    FeaturesFile formatter = new FeaturesFile();
-    format = format.toLowerCase();
-    if (format.indexOf(";") < 0)
-      format += ";includenonpositional";
-    boolean nonpos = format.indexOf(";includenonpositional") > 0;
-    boolean compl = format.indexOf(";includecomplement") >= 0;
-    if (format.startsWith("jalview"))
-    {
-      features = formatter.printJalviewFormat(
-              f.getViewport().getAlignment().getSequencesArray(),
-              f.alignPanel.getFeatureRenderer(), nonpos, compl);
-    }
-    else
-    {
-      features = formatter.printGffFormat(
-              f.getViewport().getAlignment().getSequencesArray(),
-              f.alignPanel.getFeatureRenderer(), nonpos, compl);
-    }
-
-    if (features == null)
-    {
-      features = "";
-    }
-    return features;
-
-  }
-
-  public String getAnnotationFrom(AlignFrame alf)
-  {
-    AlignFrame f = alf;
-    String annotation = new AnnotationFile()
-            .printAnnotationsForView(f.getViewport());
-    return annotation;
-  }
-
-  // public AlignFrame newViewFrom(AlignFrame alf, String name)
-  // {
-  // return (AlignFrame) alf.newView(name, true);
-  // }
-  //
-  public String[] separatorListToArray(String list)
-  {
-    return separatorListToArray(list, separator);
-  }
-
-  public Object[] getSelectionForListener(AlignFrame currentFrame,
-          SequenceGroup seqsel, ColumnSelection colsel,
-          HiddenColumns hidden, SelectionSource source, Object alignFrame)
-  {
-    // System.err.println("Testing selection event relay to
-    // jsfunction:"+_listener);
-    String setid = "";
-    AlignFrame src = (AlignFrame) alignFrame;
-    if (source != null)
-    {
-      if (source instanceof AlignViewport
-              && currentFrame.getViewport() == source)
-      {
-        // should be valid if it just generated an event!
-        src = currentFrame;
-
-      }
-    }
-    String[] seqs = new String[] {};
-    String[] cols = new String[] {};
-    int strt = 0, end = (src == null) ? -1
-            : src.alignPanel.av.getAlignment().getWidth();
-    if (seqsel != null && seqsel.getSize() > 0)
-    {
-      seqs = new String[seqsel.getSize()];
-      for (int i = 0; i < seqs.length; i++)
-      {
-        seqs[i] = seqsel.getSequenceAt(i).getName();
-      }
-      if (strt < seqsel.getStartRes())
-      {
-        strt = seqsel.getStartRes();
-      }
-      if (end == -1 || end > seqsel.getEndRes())
-      {
-        end = seqsel.getEndRes();
-      }
-    }
-    if (colsel != null && !colsel.isEmpty())
-    {
-      if (end == -1)
-      {
-        end = colsel.getMax() + 1;
-      }
-      cols = new String[colsel.getSelected().size()];
-      for (int i = 0; i < cols.length; i++)
-      {
-        cols[i] = "" + (1 + colsel.getSelected().get(i).intValue());
-      }
-    }
-    else
-    {
-      if (seqsel != null && seqsel.getSize() > 0)
-      {
-        // send a valid range, otherwise we send the empty selection
-        cols = new String[2];
-        cols[0] = "" + (1 + strt) + "-" + (1 + end);
-      }
-    }
-    return new Object[] { src, setid, arrayToSeparatorList(seqs),
-        arrayToSeparatorList(cols) };
-  }
-
-}
\ No newline at end of file
index 6f69f8a..61b3487 100644 (file)
@@ -36,11 +36,11 @@ public class JalviewJS2
       args = new String[] {
         //  "headless",
           "open", "examples/uniref50.fa",
-          "features",
-          "examples/exampleFeatures.txt"
-          , "noannotation"
-          , "showoverview"
-        //  , "png", "test-bh.png"
+//          "features",
+//          "examples/exampleFeatures.txt"
+//          , "noannotation"
+          //, "showoverview"
+          //, "png", "test-bh.png"
       };
     }
 
diff --git a/src/jalview/bin/JalviewJSApi.java b/src/jalview/bin/JalviewJSApi.java
deleted file mode 100644 (file)
index a21bebe..0000000
+++ /dev/null
@@ -1,693 +0,0 @@
-package jalview.bin;
-
-import jalview.gui.AlignFrame;
-
-/**
- * JAL-3369 JalviewJS API BH 2019.07.17
- * 
- * @author hansonr
- *
- */
-public interface JalviewJSApi
-{
-
-  void showOverview();
-
-  /**
-   * process commandline arguments after the JavaScript application has started
-   * 
-   * @param args
-   * @return
-   */
-  Object parseArguments(String[] args);
-
-
-  /**
-   * Open a new Tree panel on the desktop statically. Params are standard (not
-   * set by Groovy). No dialog is opened.
-   * 
-   * @param af
-   *          may be null
-   * @param treeType
-   * @param modelName
-   * @return null, or the string "label.you_need_at_least_n_sequences" if number
-   *         of sequences selected is inappropriate
-   */
-  public Object openTreePanel(AlignFrame af, String treeType,
-          String modelName);
-
-  /**
-   * public static method for JalviewJS API to open a PCAPanel without
-   * necessarily using a dialog.
-   * 
-   * @param af
-   *          may be null
-   * @param modelName
-   * @return the PCAPanel, or the string "label.you_need_at_least_n_sequences"
-   *         if number of sequences selected is inappropriate
-   */
-  public Object openPcaPanel(AlignFrame af, String modelName);
-
-  /**
-   * The following public methods may be called externally, eg via javascript in
-   * an HTML page.
-   * 
-   * <br>
-   * <em>TODO: introduce abstract interface for
-   * jalview.appletgui.AlignFrame</em><br>
-   * 
-   * Most function arguments are strings, which contain serialised versions of
-   * lists. Lists of things are separated by a separator character - either the
-   * default or a user supplied one. Ranges and positions on an alignment or
-   * sequence can be specified as a list, where an item containing a single
-   * number is a single position, and an item like 1-2 specifies columns 1 and 2
-   * as a range.
-   */
-
-  // /**
-  // * @author jimp
-  // *
-  // */
-  // public interface JalviewLiteJsApi
-  // {
-
-    /**
-     * @return String list of selected sequence IDs, each terminated by the
-     *         'boolean not' character (""+0x00AC); or (&#172;);
-     */
-
-    public abstract String getSelectedSequences();
-
-    /**
-     * @param sep
-     *          separator string or null for default
-     * @return String list of selected sequence IDs, each terminated by given
-     *         separator string
-     */
-
-    public abstract String getSelectedSequences(String sep);
-
-    /**
-     * @param alf
-     *          AlignFrame containing selection
-     * @return String list of selected sequence IDs, each terminated by current
-     *         default separator sequence
-     * 
-     */
-    public abstract String getSelectedSequencesFrom(AlignFrame alf);
-
-    /**
-     * get list of selected sequence IDs separated by given separator
-     * 
-     * @param alf
-     *          window containing selection
-     * @param sep
-     *          separator string to use - default is 'boolean not'
-     * @return String list of selected sequence IDs, each terminated by the given
-     *         separator
-     */
-    public abstract String getSelectedSequencesFrom(AlignFrame alf,
-            String sep);
-
-    /**
-     * 
-     * @param sequenceId
-     *          id of sequence to highlight
-     * @param position
-     *          integer position [ tobe implemented or range ] on sequence
-     * @param alignedPosition
-     *          true/false/empty string - indicate if position is an alignment
-     *          column or unaligned sequence position
-     */
-
-    public abstract void highlight(String sequenceId, String position,
-            String alignedPosition);
-
-    /**
-     * 
-     * @param sequenceId
-     *          id of sequence to highlight
-     * @param position
-     *          integer position [ tobe implemented or range ] on sequence
-     * @param alignedPosition
-     *          false, blank or something else - indicate if position is an
-     *          alignment column or unaligned sequence position
-     */
-    public abstract void highlightIn(AlignFrame alf, String sequenceId,
-            String position, String alignedPosition);
-
-    /**
-     * select regions of the currrent alignment frame
-     * 
-     * @param sequenceIds
-     *          String separated list of sequence ids or empty string
-     * @param columns
-     *          String separated list { column range or column, ..} or empty
-     *          string
-     */
-
-    public abstract void select(String sequenceIds, String columns);
-
-    /**
-     * select regions of the currrent alignment frame
-     * 
-     * @param toselect
-     *          String separated list { column range, seq1...seqn sequence ids }
-     * @param sep
-     *          separator between toselect fields
-     */
-
-    public abstract void select(String sequenceIds, String columns,
-            String sep);
-
-    /**
-     * select regions of the given alignment frame
-     * 
-     * @param alf
-     * @param toselect
-     *          String separated list { column range, seq1...seqn sequence ids }
-     * @param sep
-     *          separator between toselect fields
-     */
-    public abstract void selectIn(AlignFrame alf, String sequenceIds,
-            String columns);
-
-    /**
-     * select regions of the given alignment frame
-     * 
-     * @param alf
-     * @param toselect
-     *          String separated list { column range, seq1...seqn sequence ids }
-     * @param sep
-     *          separator between toselect fields
-     */
-    public abstract void selectIn(AlignFrame alf, String sequenceIds,
-            String columns, String sep);
-
-    /**
-     * get sequences selected in current AlignFrame and return their alignment in
-     * format 'format' either with or without suffix
-     * 
-     * @param alf
-     *          - where selection is
-     * @param format
-     *          - format of alignment file
-     * @param suffix
-     *          - "true" to append /start-end string to each sequence ID
-     * @return selected sequences as flat file or empty string if there was no
-     *         current selection
-     */
-
-    public abstract String getSelectedSequencesAsAlignment(String format,
-            String suffix);
-
-    /**
-     * get sequences selected in alf and return their alignment in format 'format'
-     * either with or without suffix
-     * 
-     * @param alf
-     *          - where selection is
-     * @param format
-     *          - format of alignment file
-     * @param suffix
-     *          - "true" to append /start-end string to each sequence ID
-     * @return selected sequences as flat file or empty string if there was no
-     *         current selection
-     */
-    public abstract String getSelectedSequencesAsAlignmentFrom(
-            AlignFrame alf,
-            String format, String suffix);
-
-    /**
-     * get a separator separated list of sequence IDs reflecting the order of the
-     * current alignment
-     * 
-     * @return
-     */
-
-    public abstract String getAlignmentOrder();
-
-    /**
-     * get a separator separated list of sequence IDs reflecting the order of the
-     * alignment in alf
-     * 
-     * @param alf
-     * @return
-     */
-    public abstract String getAlignmentOrderFrom(AlignFrame alf);
-
-    /**
-     * get a sep separated list of sequence IDs reflecting the order of the
-     * alignment in alf
-     * 
-     * @param alf
-     * @param sep
-     *          - separator to use
-     * @return
-     */
-    public abstract String getAlignmentOrderFrom(AlignFrame alf,
-            String sep);
-
-    /**
-     * re-order the current alignment using the given list of sequence IDs
-     * 
-     * @param order
-     *          - sep separated list
-     * @param undoName
-     *          - string to use when referring to ordering action in undo buffer
-     * @return 'true' if alignment was actually reordered. empty string if
-     *         alignment did not contain sequences.
-     */
-
-    public abstract String orderBy(String order, String undoName);
-
-    /**
-     * re-order the current alignment using the given list of sequence IDs
-     * separated by sep
-     * 
-     * @param order
-     *          - sep separated list
-     * @param undoName
-     *          - string to use when referring to ordering action in undo buffer
-     * @param sep
-     * @return 'true' if alignment was actually reordered. empty string if
-     *         alignment did not contain sequences.
-     */
-
-    public abstract String orderBy(String order, String undoName,
-            String sep);
-
-    /**
-     * re-order the given alignment using the given list of sequence IDs separated
-     * by sep
-     * 
-     * @param alf
-     * @param order
-     *          - sep separated list
-     * @param undoName
-     *          - string to use when referring to ordering action in undo buffer
-     * @param sep
-     * @return 'true' if alignment was actually reordered. empty string if
-     *         alignment did not contain sequences.
-     */
-    public abstract String orderAlignmentBy(AlignFrame alf, String order,
-            String undoName, String sep);
-
-    /**
-     * get alignment as format (format names FASTA, BLC, CLUSTAL, MSF, PILEUP,
-     * PFAM - see jalview.io.AppletFormatAdapter for full list);
-     * 
-     * @param format
-     * @return
-     */
-
-    public abstract String getAlignment(String format);
-
-    /**
-     * get alignment displayed in alf as format
-     * 
-     * @param alf
-     * @param format
-     * @return
-     */
-    public abstract String getAlignmentFrom(AlignFrame alf, String format);
-
-    /**
-     * get alignment as format with jalview start-end sequence suffix appended
-     * 
-     * @param format
-     * @param suffix
-     * @return
-     */
-
-    public abstract String getAlignment(String format, String suffix);
-
-    /**
-     * get alignment displayed in alf as format with or without the jalview
-     * start-end sequence suffix appended
-     * 
-     * @param alf
-     * @param format
-     * @param suffix
-     * @return
-     */
-    public abstract String getAlignmentFrom(AlignFrame alf, String format,
-            String suffix);
-
-    /**
-     * add the given features or annotation to the current alignment
-     * 
-     * @param annotation
-     */
-
-    public abstract void loadAnnotation(String annotation);
-
-    /**
-     * add the given features or annotation to the given alignment view
-     * 
-     * @param alf
-     * @param annotation
-     */
-    public abstract void loadAnnotationFrom(AlignFrame alf,
-            String annotation);
-
-    /**
-     * parse the given string as a jalview feature or GFF annotation file and
-     * optionally enable feature display on the current AlignFrame
-     * 
-     * @param features
-     *          - gff or features file
-     * @param autoenabledisplay
-     *          - when true, feature display will be enabled if any features can
-     *          be parsed from the string.
-     */
-
-    public abstract void loadFeatures(String features,
-            boolean autoenabledisplay);
-
-    /**
-     * parse the given string as a jalview feature or GFF annotation file and
-     * optionally enable feature display on the given AlignFrame.
-     * 
-     * @param alf
-     * @param features
-     *          - gff or features file
-     * @param autoenabledisplay
-     *          - when true, feature display will be enabled if any features can
-     *          be parsed from the string.
-     * @return true if data parsed as features
-     */
-    public abstract boolean loadFeaturesFrom(AlignFrame alf, String features,
-            boolean autoenabledisplay);
-
-    /**
-     * get the sequence features in the given format (Jalview or GFF);
-     * 
-     * @param format
-     * @return
-     */
-
-    public abstract String getFeatures(String format);
-
-    /**
-     * get the sequence features in alf in the given format (Jalview or GFF);
-     * 
-     * @param alf
-     * @param format
-     * @return
-     */
-    public abstract String getFeaturesFrom(AlignFrame alf, String format);
-
-    /**
-     * get current alignment's annotation as an annotation file
-     * 
-     * @return
-     */
-
-    public abstract String getAnnotation();
-
-    /**
-     * get alignment view alf's annotation as an annotation file
-     * 
-     * @param alf
-     * @return
-     */
-    public abstract String getAnnotationFrom(AlignFrame alf);
-
-  // BH incompatibility here -- JalviewLite created an AlignFrame; Jalview
-  // creates an AlignmentPanel
-  // /**
-  // * create a new view and return the AlignFrame instance
-  // *
-  // * @return
-  // */
-  //
-  // public abstract AlignFrame newView();
-  //
-  // /**
-  // * create a new view named name and return the AlignFrame instance
-  // *
-  // * @param name
-  // * @return
-  // */
-  //
-  // public abstract AlignFrame newView(String name);
-  //
-  // /**
-  // * create a new view on alf and return the AlignFrame instance
-  // *
-  // * @param alf
-  // * @return
-  // */
-  // public abstract AlignFrame newViewFrom(AlignFrame alf);
-  //
-  // /**
-  // * create a new view named name on alf
-  // *
-  // * @param alf
-  // * @param name
-  // * @return
-  // */
-  // public abstract AlignFrame newViewFrom(AlignFrame alf, String name);
-
-    /**
-     * 
-     * @param text
-     *          alignment file as a string
-     * @param title
-     *          window title
-     * @return null or new alignment frame
-     */
-
-    public abstract AlignFrame loadAlignment(String text, String title);
-
-    /**
-     * register a javascript function to handle any alignment mouseover events
-     * 
-     * @param listener
-     *          name of javascript function (called with arguments
-     *          [jalview.appletgui.AlignFrame,String(sequence id);,String(column
-     *          in alignment);, String(position in sequence);]
-     */
-
-    public abstract void setMouseoverListener(String listener);
-
-    /**
-     * register a javascript function to handle mouseover events
-     * 
-     * @param af
-     *          (null or specific AlignFrame for which events are to be listened
-     *          for);
-     * @param listener
-     *          name of javascript function
-     */
-    public abstract void setMouseoverListener(AlignFrame af,
-            String listener);
-
-    /**
-     * register a javascript function to handle any alignment selection events.
-     * Events are generated when the user completes a selection event, or when the
-     * user deselects all selected regions.
-     * 
-     * @param listener
-     *          name of javascript function (called with arguments
-     *          [jalview.appletgui.AlignFrame, String(sequence set id);,
-     *          String(separator separated list of sequences which were
-     *          selected);, String(separator separated list of column ranges (i.e.
-     *          single number or hyphenated range); that were selected);]
-     */
-
-    public abstract void setSelectionListener(String listener);
-
-    public abstract void setSelectionListener(AlignFrame af,
-            String listener);
-
-    /**
-     * register a javascript function to handle events normally routed to a Jmol
-     * structure viewer.
-     * 
-     * @param listener
-     *          - javascript function (arguments are variable, see
-     *          jalview.javascript.MouseOverStructureListener for full details);
-     * @param modelSet
-     *          - separator separated list of PDB file URIs that this viewer is
-     *          handling. These files must be in the same order they appear in
-     *          Jmol (e.g. first one is frame 1, second is frame 2, etc);.
-     * @see jalview.javascript.MouseOverStructureListener
-     */
-
-    public abstract void setStructureListener(String listener,
-            String modelSet);
-
-    /**
-     * remove any callback using the given listener function and associated with
-     * the given AlignFrame (or null for all callbacks);
-     * 
-     * @param af
-     *          (may be null);
-     * @param listener
-     *          (may be null);
-     */
-    public abstract void removeJavascriptListener(AlignFrame af,
-            String listener);
-
-    /**
-     * send a mouseover message to all the alignment windows associated with the
-     * given residue in the pdbfile
-     * 
-     * @param pdbResNum
-     * @param chain
-     * @param pdbfile
-     */
-
-    public abstract void mouseOverStructure(String pdbResNum, String chain,
-            String pdbfile);
-
-    /**
-     * bind a pdb file to a sequence in the given AlignFrame.
-     * 
-     * @param alFrame
-     *          - null or specific AlignFrame. This specifies the dataset that
-     *          will be searched for a seuqence called sequenceId
-     * @param sequenceId
-     *          - sequenceId within the dataset.
-     * @param pdbEntryString
-     *          - the short name for the PDB file
-     * @param pdbFile
-     *          - pdb file - either a URL or a valid PDB file.
-     * @return true if binding was as success TODO: consider making an exception
-     *         structure for indicating when PDB parsing or sequenceId location
-     *         fails.
-     */
-    public abstract boolean addPdbFile(AlignFrame alFrame, String sequenceId,
-            String pdbEntryString, String pdbFile);
-
-    /**
-     * adjust horizontal/vertical scroll to make the given location the top left
-     * hand corner for the given view
-     * 
-     * @param alf
-     * @param topRow
-     * @param leftHandColumn
-     */
-    public abstract void scrollViewToIn(AlignFrame alf, String topRow,
-            String leftHandColumn);
-
-    /**
-     * adjust vertical scroll to make the given row the top one for given view
-     * 
-     * @param alf
-     * @param topRow
-     */
-    public abstract void scrollViewToRowIn(AlignFrame alf, String topRow);
-
-    /**
-     * adjust horizontal scroll to make the given column the left one in the given
-     * view
-     * 
-     * @param alf
-     * @param leftHandColumn
-     */
-    public abstract void scrollViewToColumnIn(AlignFrame alf,
-            String leftHandColumn);
-
-    /**
-     * 
-     * @return
-     * @see jalview.appletgui.AlignFrame#getFeatureGroups();
-     */
-
-    public abstract String getFeatureGroups();
-
-    /**
-     * @param alf
-     *          AlignFrame to get feature groups on
-     * @return
-     * @see jalview.appletgui.AlignFrame#getFeatureGroups();
-     */
-    public abstract String getFeatureGroupsOn(AlignFrame alf);
-
-    /**
-     * @param visible
-     * @return
-     * @see jalview.appletgui.AlignFrame#getFeatureGroupsOfState(boolean);
-     */
-
-    public abstract String getFeatureGroupsOfState(boolean visible);
-
-    /**
-     * @param alf
-     *          align frame to get groups of state visible
-     * @param visible
-     * @return
-     * @see jalview.appletgui.AlignFrame#getFeatureGroupsOfState(boolean);
-     */
-    public abstract String getFeatureGroupsOfStateOn(AlignFrame alf,
-            boolean visible);
-
-    /**
-     * @param groups
-     *          tab separated list of group names
-     * @param state
-     *          true or false
-     * @see jalview.appletgui.AlignFrame#setFeatureGroupState(java.lang.String[],
-     *      boolean);
-     */
-    public abstract void setFeatureGroupStateOn(AlignFrame alf,
-            String groups,
-            boolean state);
-
-
-    public abstract void setFeatureGroupState(String groups, boolean state);
-
-    /**
-     * List separator string
-     * 
-     * @return the separator
-     */
-
-    public abstract String getSeparator();
-
-    /**
-     * List separator string
-     * 
-     * @param separator
-     *          the separator to set. empty string will reset separator to default
-     */
-
-    public abstract void setSeparator(String separator);
-
-    /**
-     * Retrieve fragments of a large packet of data made available by JalviewLite.
-     * 
-     * @param messageclass
-     * @param viewId
-     * @return next chunk of message
-     */
-
-    public abstract String getJsMessage(String messageclass, String viewId);
-
-    /// in http://www.jalview.org/examples/jalviewLiteJs.html but missing here
-
-    // get selected sequences as alignment as format with or without start-end
-    // suffix
-
-    public String getSelectedSequencesAsAlignment(String format,
-            boolean suffix);
-
-    // get selected sequences as alignment from given view as format with or
-    // without start-end suffix
-    public String getSelectedSequencesAsAlignmentFrom(AlignFrame alf,
-            String format, boolean suffix);
-
-
-    public String arrayToSeparatorList(String[] array);
-
-    // get a string array from a list
-
-    public String[] separatorListToArray(String list);
-
-    // debug flag - controls output to standard out
-    public static boolean debug = false;
-
-}
diff --git a/src/jalview/bin/JalviewJSApp.java b/src/jalview/bin/JalviewJSApp.java
new file mode 100644 (file)
index 0000000..feab4ae
--- /dev/null
@@ -0,0 +1,1691 @@
+package jalview.bin;
+
+import java.awt.EventQueue;
+//import java.applet.AppletContext;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.swing.SwingUtilities;
+
+import jalview.api.JalviewJSApi;
+import jalview.api.StructureSelectionManagerProvider;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentOrder;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
+import jalview.gui.CalculationChooser;
+import jalview.gui.Desktop;
+import jalview.gui.StructureViewer;
+import jalview.io.AnnotationFile;
+import jalview.io.AppletFormatAdapter;
+import jalview.io.DataSourceType;
+import jalview.io.FeaturesFile;
+import jalview.io.FileFormat;
+import jalview.io.FileFormatI;
+import jalview.io.FileFormats;
+import jalview.io.IdentifyFile;
+import jalview.io.JPredFile;
+import jalview.io.JnetAnnotationMaker;
+import jalview.io.NewickFile;
+import jalview.structure.SelectionListener;
+import jalview.structure.SelectionSource;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.HttpUtils;
+import jalview.util.Platform;
+
+/**
+ * Basically the JalviewLite application, but without JalviewLite
+ * 
+ * Processing all "applet parameters" and also all "applet interface" methods.
+ * 
+ * @author hansonr
+ *
+ */
+public class JalviewJSApp implements JalviewJSApi
+{
+  private ArgsParser aparser;
+
+  private String[] ret = new String[1];
+
+  // private boolean alignPDBStructures; From JalviewLite; not implemented
+
+  private String separator = "\u00AC"; // JalviewLite note: the default used to
+                                       // be '|', but many sequence IDS include
+                                       // pipes.
+
+  /**
+   * We maintain a pointer to the jalview instance here, because only with that
+   * do we have a direct connection from the JavaScript "applet" object to the
+   * proper instance of Jalview in case there are multiple applets on a page.
+   */
+  private Jalview jalview;
+
+  public class JsSelectionListener
+          implements jalview.structure.SelectionListener
+  {
+
+    AlignFrame _alf;
+
+    String _listener;
+
+    public JsSelectionListener(AlignFrame alf, String listener)
+    {
+      _alf = alf;
+      _listener = listener;
+    }
+
+    public boolean isFor(AlignFrame alf, String listener)
+    {
+      return (_alf == null || _alf == alf) && _listener.equals(listener);
+    }
+
+    @Override
+    public void selection(SequenceGroup seqsel, ColumnSelection colsel,
+            HiddenColumns hidden, SelectionSource source)
+    {
+      // System.err.println("Testing selection event relay to
+      // jsfunction:"+_listener);
+      String setid = "";
+      AlignFrame srcFrame = (_alf == null ? getCurrentAlignFrame() : _alf);
+      if (source != null)
+      {
+        if (source instanceof AlignViewport
+                && srcFrame.getViewport() != source)
+        {
+          return;
+        }
+      }
+      String[] seqs = new String[] {};
+      String[] cols = new String[] {};
+      int strt = 0, end = (srcFrame == null) ? -1
+              : srcFrame.alignPanel.av.getAlignment().getWidth();
+      if (seqsel != null && seqsel.getSize() > 0)
+      {
+        seqs = new String[seqsel.getSize()];
+        for (int i = 0; i < seqs.length; i++)
+        {
+          seqs[i] = seqsel.getSequenceAt(i).getName();
+        }
+        if (strt < seqsel.getStartRes())
+        {
+          strt = seqsel.getStartRes();
+        }
+        if (end == -1 || end > seqsel.getEndRes())
+        {
+          end = seqsel.getEndRes();
+        }
+      }
+      if (colsel != null && !colsel.isEmpty())
+      {
+        if (end == -1)
+        {
+          end = colsel.getMax() + 1;
+        }
+        cols = new String[colsel.getSelected().size()];
+        for (int i = 0; i < cols.length; i++)
+        {
+          cols[i] = "" + (1 + colsel.getSelected().get(i).intValue());
+        }
+      }
+      else
+      {
+        if (seqsel != null && seqsel.getSize() > 0)
+        {
+          // send a valid range, otherwise we send the empty selection
+          cols = new String[1];
+          cols[0] = "" + (1 + strt) + "-" + (1 + end);
+        }
+      }
+      doSendCallback(_listener,
+              new Object[]
+              { Jalview.getInstance().j2sAppletID, srcFrame, source, setid,
+                  seqs, cols });
+    }
+
+  }
+
+  public JalviewJSApp(Jalview jalview, ArgsParser aparser)
+  {
+    Platform.setAppClass(this);
+    this.jalview = jalview;
+    this.aparser = aparser;
+  }
+
+  @Override
+  public boolean addPdbFile(String sequenceId, String pdbId, String pdbFile,
+          AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    SequenceI seq = alf.getViewport().getAlignment().findName(sequenceId);
+    if (seq != null)
+    {
+      Vector<PDBEntry> pdbe = seq.getAllPDBEntries();
+      PDBEntry pdbentry = null;
+      if (pdbe != null && pdbe.size() > 0)
+      {
+        for (int pe = 0, peSize = pdbe.size(); pe < peSize; pe++)
+        {
+          pdbentry = pdbe.elementAt(pe);
+          if (!pdbentry.getId().equals(pdbId)
+                  || pdbFile != null && !pdbentry.getFile().equals(pdbFile))
+          {
+            pdbentry = null;
+          }
+        }
+      }
+      if (pdbentry == null)
+      {
+        pdbentry = new PDBEntry(pdbId, null, pdbFile);
+        if (pdbFile != null)
+        {
+          DataSourceType protocol = AppletFormatAdapter
+                  .resolveProtocol(pdbFile, FileFormat.PDB);
+          if (protocol == null)
+            return false;
+          pdbentry.setProperty("protocol", protocol);
+        }
+        seq.addPDBId(pdbentry);
+        alf.alignPanel.getStructureSelectionManager()
+                .registerPDBEntry(pdbentry);
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public String getAlignment(String format, boolean addSuffix,
+          AlignFrame alf)
+  {
+    try
+    {
+      if (alf == null)
+      {
+        alf = getCurrentAlignFrame();
+      }
+
+      FileFormatI theFormat = FileFormats.getInstance().forName(format);
+      String reply = new AppletFormatAdapter().formatSequences(theFormat,
+              alf.getViewport().getAlignment(), addSuffix);
+      return reply;
+    } catch (IllegalArgumentException ex)
+    {
+      ex.printStackTrace();
+      return "Error retrieving alignment, possibly invalid format specifier: "
+              + format;
+    }
+  }
+
+  @Override
+  public String[] getAlignmentOrder(AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    AlignmentI alorder = alf.getViewport().getAlignment();
+    String[] order = new String[alorder.getHeight()];
+    for (int i = 0; i < order.length; i++)
+    {
+      order[i] = alorder.getSequenceAt(i).getName();
+    }
+    return order;// arrayToSeparatorList(order, sep);
+  }
+
+  @Override
+  public String getAnnotation(AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    String annotation = new AnnotationFile()
+            .printAnnotationsForView(alf.getViewport());
+    return annotation;
+  }
+
+  /**
+   * Get the applet-like code base even though this is an application.
+   */
+
+  @Override
+  public URL getCodeBase()
+  {
+    return Platform.getCodeBase();
+  }
+
+  @Override
+  public AlignFrame getCurrentAlignFrame()
+  {
+    // if (jalview != Jalview.getInstance() || jalview.currentAlignFrame !=
+    // Jalview.getCurrentAlignFrame()) {
+    // /** @j2sNative debugger */
+    // }
+    return jalview.currentAlignFrame;
+  }
+
+  /**
+   * Get the applet-like document base even though this is an application.
+   */
+
+  @Override
+  public URL getDocumentBase()
+  {
+    return Platform.getDocumentBase();
+  }
+
+  @Override
+  public String[] getFeatureGroups(AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    return alf.getFeatureGroups();
+  }
+
+  @Override
+  public String[] getFeatureGroupsOfState(boolean visible, AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    return alf.getFeatureGroupsOfState(visible);
+  }
+
+  /**
+   * JavaScript interface to print the alignment frame
+   * 
+   * @param format
+   *          "jalview" or "gff" with or without ";includeComplement" or
+   *          ";includeNonpositional"; default with no ";" is
+   *          ";includeNonpositional"
+   * @param alf
+   * 
+   * @return
+   */
+  @Override
+  public String getFeatures(String format, AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    String features;
+    FeaturesFile formatter = new FeaturesFile();
+    format = format.toLowerCase();
+    if (format.indexOf(";") < 0)
+      format += ";includenonpositional";
+    boolean nonpos = format.indexOf(";includenonpositional") >= 0;
+    boolean compl = format.indexOf(";includecomplement") >= 0;
+    if (format.startsWith("jalview"))
+    {
+      features = formatter.printJalviewFormat(
+              alf.getViewport().getAlignment().getSequencesArray(),
+              alf.alignPanel.getFeatureRenderer(), nonpos, compl);
+    }
+    else
+    {
+      features = formatter.printGffFormat(
+              alf.getViewport().getAlignment().getSequencesArray(),
+              alf.alignPanel.getFeatureRenderer(), nonpos, compl);
+    }
+
+    if (features == null)
+    {
+      features = "";
+    }
+    return features;
+
+  }
+
+  /**
+   * Get an applet parameter as a string.
+   * 
+   */
+  @Override
+  public String getParameter(String name)
+  {
+    return (String) aparser.getAppletValue(name, null, true);
+  }
+
+  /**
+   * Get an applet parameter as an Object.
+   */
+
+  @Override
+  public Object getParameterAsObject(String name)
+  {
+    return aparser.getAppletValue(name, null, false);
+  }
+
+  /**
+   * read sequence1...sequenceN as a raw alignment
+   * 
+   * @param jalviewApp
+   * @return
+   */
+  public String getPastedSequence(JalviewJSApp jalviewApp)
+  {
+    StringBuffer data = new StringBuffer("PASTE");
+    int i = 1;
+    String file = null;
+    while ((file = getParameter("sequence" + i)) != null)
+    {
+      data.append(file.toString() + "\n");
+      i++;
+    }
+    if (data.length() > 5)
+    {
+      file = data.toString();
+    }
+    return file;
+  }
+
+  /**
+   * @j2sAlias getSelectedSequences
+   * 
+   * @see jalview.appletgui.js.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
+   *      .AlignFrame)
+   */
+  @Override
+  public SequenceI[] getSelectedSequences(AlignFrame alf)
+  {
+    // return getSelectedSequencesFrom(alf, null);
+    // }
+    //
+    // @Override
+    // public SequenceI[] getSelectedSequencesFrom(AlignFrame alf, String sep)
+    // {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    AlignViewport v = alf.getViewport();
+    if (v.getSelectionGroup() != null)
+    {
+      return v.getSelectionGroup().getSequencesInOrder(v.getAlignment());
+    }
+    return null;
+  }
+  // /**
+  // *
+  // * @see
+  // jalview.appletgui.js.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
+  // * .AlignFrame, java.lang.String)
+  // */
+  // @Override
+  // public void highlight(String sequenceId, String position,
+  // String alignedPosition)
+  // {
+  // highlightIn(null, sequenceId, position, alignedPosition);
+  // }
+
+  /**
+   * @j2sAlias getSelectedSequencesAsAlignment
+   */
+  @Override
+  public String getSelectedSequencesAsAlignment(String format,
+          boolean addSuffix, AlignFrame alf)
+  {
+
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    try
+    {
+      AlignViewport vp = alf.getViewport();
+      FileFormatI theFormat = FileFormats.getInstance().forName(format);
+      if (vp.getSelectionGroup() != null)
+      {
+        // JBPNote: getSelectionAsNewSequence behaviour has changed - this
+        // method now returns a full copy of sequence data
+        // TODO consider using getSequenceSelection instead here
+        String reply = new AppletFormatAdapter().formatSequences(theFormat,
+                new Alignment(vp.getSelectionAsNewSequence()), addSuffix);
+        return reply;
+      }
+    } catch (IllegalArgumentException ex)
+    {
+      ex.printStackTrace();
+      return "Error retrieving alignment, possibly invalid format specifier: "
+              + format;
+    }
+    return "";
+  }
+
+  @Override
+  public void highlight(String sequenceId, String position,
+          String alignedPosition, AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    // TODO: could try to highlight in all alignments if alf==null
+    jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
+            alf.getViewport().getAlignment().getSequencesArray());
+    final SequenceI sq = matcher.findIdMatch(sequenceId);
+    if (sq != null)
+    {
+      int apos = -1;
+      try
+      {
+        apos = Integer.valueOf(position).intValue();
+        apos--;
+      } catch (NumberFormatException ex)
+      {
+        return;
+      }
+      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))
+      {
+        java.awt.EventQueue.invokeLater(new Runnable()
+        {
+          @Override
+          public void run()
+          {
+            StructureSelectionManager
+                    .getStructureSelectionManager(Desktop.getInstance())
+                    .mouseOverVamsasSequence(sq, sq.findIndex(pos), null);
+          }
+        });
+      }
+      else
+      {
+        java.awt.EventQueue.invokeLater(new Runnable()
+        {
+          @Override
+          public void run()
+          {
+            StructureSelectionManager
+                    .getStructureSelectionManager(Desktop.getInstance())
+                    .mouseOverVamsasSequence(sq, pos, null);
+          }
+        });
+      }
+    }
+  }
+
+  @Override
+  public AlignFrame loadAlignment(String text, String title, int width,
+          int height)
+  {
+    AlignmentI al = null;
+
+    try
+    {
+      FileFormatI format = new IdentifyFile().identify(text,
+              DataSourceType.PASTE);
+      al = new AppletFormatAdapter().readFile(text, DataSourceType.PASTE,
+              format);
+      if (al.getHeight() > 0)
+      {
+        return new AlignFrame(al,
+                width > 0 ? width : AlignFrame.DEFAULT_WIDTH,
+                height > 0 ? height : AlignFrame.DEFAULT_HEIGHT, title);
+      }
+    } catch (IOException ex)
+    {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  @Override
+  public void loadAnnotation(String annotation, AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    if (new AnnotationFile().annotateAlignmentView(alf.getViewport(),
+            annotation, DataSourceType.PASTE))
+    {
+      alf.alignPanel.fontChanged();
+      alf.alignPanel.setScrollValues(0, 0);
+    }
+    else
+    {
+      alf.parseFeaturesFile(annotation, DataSourceType.PASTE);
+    }
+  }
+
+  @Override
+  public boolean loadFeatures(String features, boolean autoenabledisplay,
+          AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    boolean ret = alf.parseFeaturesFile(features, DataSourceType.PASTE);
+    if (!ret)
+    {
+      return false;
+    }
+    if (autoenabledisplay)
+    {
+      alf.getViewport().setShowSequenceFeatures(true);
+      // this next was for a checkbox in JalviewLite
+      // ((AlignFrame) alf).getViewport().sequenceFeatures.setState(true);
+    }
+    return true;
+  }
+
+  @Override
+  public boolean loadScoreFile(String fileName, AlignFrame alf)
+  {
+    try
+    {
+      (alf == null ? getCurrentAlignFrame() : alf)
+              .loadJalviewDataFile(fileName, null, null, null);
+      return true;
+    } catch (Throwable t)
+    {
+      return false;
+    }
+  }
+
+  /**
+   * @j2sAlias openPcaPanel
+   * 
+   *           public static method for JalviewJS API to open a PCAPanel without
+   *           necessarily using a dialog.
+   * @param modelName
+   * @param alf
+   * 
+   * @return the PCAPanel, or the string "label.you_need_at_least_n_sequences"
+   *         if number of sequences selected is inappropriate
+   */
+  @Override
+  public Object openPcaPanel(String modelName, AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    return CalculationChooser.openPcaPanel(alf, modelName, null);
+  }
+
+  /**
+   * @j2sAlias openTreePanel
+   * 
+   *           Open a new Tree panel on the desktop statically. Params are
+   *           standard (not set by Groovy). No dialog is opened.
+   * @param treeType
+   * @param modelName
+   * @param alf
+   * 
+   * @return null, or the string "label.you_need_at_least_n_sequences" if number
+   *         of sequences selected is inappropriate
+   */
+  @Override
+  public Object openTreePanel(String treeType, String modelName,
+          AlignFrame alf)
+  {
+    if (alf == null)
+    {
+      alf = getCurrentAlignFrame();
+    }
+    return CalculationChooser.openTreePanel(alf, treeType, modelName, null);
+  }
+
+  @Override
+  public boolean orderAlignment(String[] ids, String undoName,
+          AlignFrame alf)
+  {
+    if (alf == null)
+      alf = getCurrentAlignFrame();
+    SequenceI[] sqs = null;
+    if (ids != null && ids.length > 0)
+    {
+      jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
+              alf.getViewport().getAlignment().getSequencesArray());
+      int s = 0;
+      sqs = new SequenceI[ids.length];
+      for (int i = 0; i < ids.length; i++)
+      {
+        if (ids[i].trim().length() == 0)
+        {
+          continue;
+        }
+        SequenceI sq = matcher.findIdMatch(ids[i]);
+        if (sq != null)
+        {
+          sqs[s++] = sq;
+        }
+      }
+      if (s > 0)
+      {
+        SequenceI[] sqq = new SequenceI[s];
+        System.arraycopy(sqs, 0, sqq, 0, s);
+        sqs = sqq;
+      }
+      else
+      {
+        sqs = null;
+      }
+    }
+    if (sqs == null)
+    {
+      return false;
+    }
+    ;
+    final AlignmentOrder aorder = new AlignmentOrder(sqs);
+
+    if (undoName != null && undoName.trim().length() == 0)
+    {
+      undoName = null;
+    }
+    final String _undoName = undoName;
+    // TODO: deal with synchronization here: cannot raise any events until
+    // alfter
+    // this has returned.
+    return alf.sortBy(aorder, _undoName);
+  }
+
+  /**
+   * Allow an outside entity to initiate the second half of argument parsing
+   * (only).
+   * 
+   * @param args
+   * @return null is good
+   */
+  @Override
+  public Object parseArguments(String[] args)
+  {
+
+    try
+    {
+      jalview.parseArguments(new ArgsParser(args), false);
+      return null;
+    } catch (Throwable t)
+    {
+      return t;
+    }
+  }
+
+  /**
+   * @j2sAlias parseFeatureFile
+   * 
+   * @param filename
+   * @param alf
+   * @return
+   */
+  @Override
+  public boolean parseFeaturesFile(String filename, AlignFrame alf)
+  {
+    ret[0] = filename;
+    DataSourceType protocol = resolveFileProtocol(ret);
+    if (protocol == null)
+      return false;
+    return (alf == null ? getCurrentAlignFrame() : alf)
+            .parseFeaturesFile(ret[0], protocol);
+  }
+
+  @Override
+  public void removeSelectionListener(String listener, AlignFrame alf)
+  {
+
+    List<SelectionListener> listeners = Desktop
+            .getStructureSelectionManager().getListeners();
+    for (int i = listeners.size(); --i >= 0;)
+    {
+      SelectionListener l = listeners.get(i);
+      if (l instanceof JsSelectionListener
+              && ((JsSelectionListener) l).isFor(alf, listener))
+      {
+        listeners.remove(i);
+        break;
+      }
+    }
+  }
+
+  private DataSourceType resolveFileProtocol(String[] retPath)
+  {
+    String path = retPath[0];
+    /*
+     * is it paste data?
+     */
+    if (path.startsWith("PASTE"))
+    {
+      retPath[0] = path.substring(5);
+      return DataSourceType.PASTE;
+    }
+
+    /*
+     * is it a URL?
+     */
+    if (path.indexOf("://") >= 0)
+    {
+      return DataSourceType.URL;
+    }
+
+    /*
+     * try relative to document root
+     */
+    URL documentBase = getDocumentBase();
+    String withDocBase = resolveUrlForLocalOrAbsolute(path, documentBase);
+    if (HttpUtils.isValidUrl(withDocBase))
+    {
+      // if (debug)
+      // {
+      // System.err.println("Prepended document base '" + documentBase
+      // + "' to make: '" + withDocBase + "'");
+      // }
+      retPath[0] = withDocBase;
+      return DataSourceType.URL;
+    }
+
+    /*
+     * try relative to codebase (if different to document base)
+     */
+    URL codeBase = getCodeBase();
+    String withCodeBase = resolveUrlForLocalOrAbsolute(path, codeBase);
+    if (!withCodeBase.equals(withDocBase)
+            && HttpUtils.isValidUrl(withCodeBase))
+    {
+      // if (debug)
+      // {
+      // System.err.println("Prepended codebase '" + codeBase
+      // + "' to make: '" + withCodeBase + "'");
+      // }
+      retPath[0] = withCodeBase;
+      return DataSourceType.URL;
+    }
+
+    /*
+     * try locating by classloader; try this last so files in the directory
+     * are resolved using document base
+     */
+    if (inArchive(getClass(), path))
+    {
+      return DataSourceType.CLASSLOADER;
+    }
+    return null;
+  }
+
+  @Override
+  public void scrollViewTo(int topRow, int leftHandColumn, AlignFrame alf)
+  {
+    // TODO test
+    java.awt.EventQueue.invokeLater(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        try
+        {
+          (alf == null ? getCurrentAlignFrame() : alf).scrollTo(topRow,
+                  leftHandColumn);
+        } catch (Exception ex)
+        {
+          System.err.println("Couldn't parse integer arguments (topRow='"
+                  + topRow + "' and leftHandColumn='" + leftHandColumn
+                  + "')");
+          ex.printStackTrace();
+        }
+      }
+    });
+  }
+
+  @Override
+  public void select(String ids[], String cols[], AlignFrame alf)
+  {
+    if (alf == null)
+      alf = getCurrentAlignFrame();
+    final SequenceGroup sel = new SequenceGroup();
+    final ColumnSelection csel = new ColumnSelection();
+    AlignmentI al = alf.getViewport().getAlignment();
+    jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
+            alf.getViewport().getAlignment().getSequencesArray());
+    int start = 0, end = al.getWidth(), alw = al.getWidth();
+    boolean seqsfound = true;
+    if (ids != null && ids.length > 0)
+    {
+      seqsfound = false;
+      for (int i = 0; i < ids.length; i++)
+      {
+        if (ids[i].trim().length() == 0)
+        {
+          continue;
+        }
+        SequenceI sq = matcher.findIdMatch(ids[i]);
+        if (sq != null)
+        {
+          seqsfound = true;
+          sel.addSequence(sq, false);
+        }
+      }
+    }
+    boolean inseqpos = false;
+    if (cols != null && cols.length > 0)
+    {
+      boolean seset = false;
+      for (int i = 0; i < cols.length; i++)
+      {
+        String cl = cols[i].trim();
+        if (cl.length() == 0)
+        {
+          continue;
+        }
+        int p;
+        if ((p = cl.indexOf("-")) > -1)
+        {
+          int from = -1, to = -1;
+          try
+          {
+            from = Integer.valueOf(cl.substring(0, p)).intValue();
+            from--;
+          } catch (NumberFormatException ex)
+          {
+            System.err.println(
+                    "ERROR: Couldn't parse first integer in range element column selection string '"
+                            + cl + "' - format is 'from-to'");
+            return;
+          }
+          try
+          {
+            to = Integer.valueOf(cl.substring(p + 1)).intValue();
+            to--;
+          } catch (NumberFormatException ex)
+          {
+            System.err.println(
+                    "ERROR: Couldn't parse second integer in range element column selection string '"
+                            + cl + "' - format is 'from-to'");
+            return;
+          }
+          if (from >= 0 && to >= 0)
+          {
+            // valid range
+            if (from < to)
+            {
+              int t = to;
+              to = from;
+              to = t;
+            }
+            if (!seset)
+            {
+              start = from;
+              end = to;
+              seset = true;
+            }
+            else
+            {
+              // comment to prevent range extension
+              if (start > from)
+              {
+                start = from;
+              }
+              if (end < to)
+              {
+                end = to;
+              }
+            }
+            for (int r = from; r <= to; r++)
+            {
+              if (r >= 0 && r < alw)
+              {
+                csel.addElement(r);
+              }
+            }
+          }
+          else
+          {
+            System.err.println("ERROR: Invalid Range '" + cl
+                    + "' deparsed as [" + from + "," + to + "]");
+          }
+        }
+        else
+        {
+          int r = -1;
+          try
+          {
+            r = Integer.valueOf(cl).intValue();
+            r--;
+          } catch (NumberFormatException ex)
+          {
+            if (cl.toLowerCase().equals("sequence"))
+            {
+              // we are in the dataset sequence's coordinate frame.
+              inseqpos = true;
+            }
+            else
+            {
+              System.err.println(
+                      "ERROR: Couldn't parse integer from point selection element of column selection string '"
+                              + cl + "'");
+              return;
+            }
+          }
+          if (r >= 0 && r <= alw)
+          {
+            if (!seset)
+            {
+              start = r;
+              end = r;
+              seset = true;
+            }
+            else
+            {
+              // comment to prevent range extension
+              if (start > r)
+              {
+                start = r;
+              }
+              if (end < r)
+              {
+                end = r;
+              }
+            }
+            csel.addElement(r);
+          }
+          else
+          {
+            System.err.println("ERROR: Invalid Point selection '" + cl
+                    + "' deparsed as [" + r + "]");
+          }
+        }
+      }
+    }
+    if (seqsfound)
+    {
+      // we only propagate the selection when it was the null selection, or the
+      // given sequences were found in the alignment.
+      if (inseqpos && sel.getSize() > 0)
+      {
+        // assume first sequence provides reference frame ?
+        SequenceI rs = sel.getSequenceAt(0);
+        start = rs.findIndex(start);
+        end = rs.findIndex(end);
+        List<Integer> cs = new ArrayList<>(csel.getSelected());
+        csel.clear();
+        for (Integer selectedCol : cs)
+        {
+          csel.addElement(rs.findIndex(selectedCol));
+        }
+      }
+      sel.setStartRes(start);
+      sel.setEndRes(end);
+      AlignFrame af = alf;
+      EventQueue.invokeLater(new Runnable()
+      {
+        @Override
+        public void run()
+        {
+          af.select(sel, csel,
+                  af.getCurrentView().getAlignment().getHiddenColumns());
+        }
+      });
+    }
+  }
+
+  //
+  // @Override
+  // public void setFeatureGroupState(String[] groups, boolean state)
+  // {
+  // setFeatureGroupState(null, groups, state);
+  // }
+  //
+  // @Override
+  // public void setFeatureGroupState(String[] groups, boolean state)
+  // { // JalviewLite API
+  // setFeatureGroupStateOn(null, groups, state);
+  // }
+  //
+  @Override
+  public void setFeatureGroupState(final String[] groups,
+          boolean state, AlignFrame alf)
+  {
+    // setFeatureGroupState(alf, groups, state);
+    // java.awt.EventQueue.invokeLater(new Runnable()
+    // {
+    // @Override
+    // public void run()
+    // {
+    // (alf == null ? getCurrentAlignFrame() : alf)
+    // .setFeatureGroupState(
+    // separatorListToArray(groups, separator), state);
+    // }
+    // });
+    // }
+    //
+    // public void setFeatureGroupState(AlignFrame alf, String[] groups, boolean
+    // state) {
+    (alf == null ? getCurrentAlignFrame() : alf)
+            .setFeatureGroupState(groups, state);
+  }
+
+  @Override
+  public void setSelectionListener(String listener, AlignFrame alf)
+  {
+    Desktop.getStructureSelectionManager()
+            .addSelectionListener(new JsSelectionListener(alf, listener));
+  }
+
+  @Override
+  public void showOverview()
+  {
+    getCurrentAlignFrame().overviewMenuItem_actionPerformed(null);
+  }
+
+  /**
+   * @j2sAlias showStructure
+   */
+  @Override
+  public void showStructure(String pdbID, String fileType, AlignFrame alf)
+  {
+    if (alf == null)
+      alf = getCurrentAlignFrame();
+    PDBEntry pe = null;
+    SequenceI[] seqs = null;
+    if (pdbID == null)
+    {
+      seqs = alf.getViewport().getSequenceSelection();
+      if (seqs.length == 0)
+        seqs = alf.getViewport().getAlignment().getSequencesArray();
+      for (int i = 0; i < seqs.length; i++)
+      {
+        Vector<PDBEntry> list = seqs[i].getAllPDBEntries();
+        if (list.size() > 0)
+        {
+          pe = list.get(0);
+          break;
+        }
+      }
+    }
+    if (pe == null)
+    {
+      if (pdbID == null)
+        return;
+      pe = new PDBEntry(pdbID, null, fileType);
+      List<SequenceI> list = alf.getViewport().getAlignment()
+              .getSequences();
+      List<SequenceI> tmp = new ArrayList<SequenceI>();
+      for (int i = 0; i < list.size(); i++)
+      {
+        SequenceI seq = list.get(i);
+        if (seq.getPDBEntry(pdbID) != null)
+        {
+          tmp.add(seq);
+        }
+      }
+      seqs = tmp.toArray(new SequenceI[tmp.size()]);
+      alf.alignPanel.selectSequences(tmp);
+    }
+    StructureViewer.launchStructureViewer(alf.alignPanel, pe, seqs);
+  }
+
+  // private or package-private methods
+
+  /**
+   * form a complete URL given a path to a resource and a reference location on
+   * the same server
+   * 
+   * @param targetPath
+   *          - an absolute path on the same server as localref or a document
+   *          located relative to localref
+   * @param localref
+   *          - a URL on the same server as url
+   * @return a complete URL for the resource located by url
+   */
+  private static String resolveUrlForLocalOrAbsolute(String targetPath,
+          URL localref)
+  {
+    String resolvedPath = "";
+    if (targetPath.startsWith("/"))
+    {
+      String codebase = localref.toString();
+      String localfile = localref.getFile();
+      resolvedPath = codebase.substring(0,
+              codebase.length() - localfile.length()) + targetPath;
+      return resolvedPath;
+    }
+
+    /*
+     * get URL path and strip off any trailing file e.g.
+     * www.jalview.org/examples/index.html#applets?a=b is trimmed to
+     * www.jalview.org/examples/
+     */
+    String urlPath = localref.toString();
+    String directoryPath = urlPath;
+    int lastSeparator = directoryPath.lastIndexOf("/");
+    if (lastSeparator > 0)
+    {
+      directoryPath = directoryPath.substring(0, lastSeparator + 1);
+    }
+
+    if (targetPath.startsWith("/"))
+    {
+      /*
+       * construct absolute URL to a file on the server - this is not allowed?
+       */
+      // String localfile = localref.getFile();
+      // resolvedPath = urlPath.substring(0,
+      // urlPath.length() - localfile.length())
+      // + targetPath;
+      resolvedPath = directoryPath + targetPath.substring(1);
+    }
+    else
+    {
+      resolvedPath = directoryPath + targetPath;
+    }
+    // if (debug)
+    // {
+    // System.err.println(
+    // "resolveUrlForLocalOrAbsolute returning " + resolvedPath);
+    // }
+    return resolvedPath;
+  }
+
+  /**
+   * parse the string into a list
+   * 
+   * @param list
+   * @param separator
+   * @return elements separated by separator
+   */
+  private static String[] separatorListToArray(String list,
+          String separator)
+  {
+    // TODO use StringUtils version (slightly different...)
+    int seplen = separator.length();
+    if (list == null || list.equals("") || list.equals(separator))
+    {
+      return null;
+    }
+    Vector<String> jv = new Vector<>();
+    int cp = 0, pos;
+    while ((pos = list.indexOf(separator, cp)) > cp)
+    {
+      jv.addElement(list.substring(cp, pos));
+      cp = pos + seplen;
+    }
+    if (cp < list.length())
+    {
+      String c = list.substring(cp);
+      if (!c.equals(separator))
+      {
+        jv.addElement(c);
+      }
+    }
+    if (jv.size() > 0)
+    {
+      String[] v = new String[jv.size()];
+      for (int i = 0; i < v.length; i++)
+      {
+        v[i] = jv.elementAt(i);
+      }
+      jv.removeAllElements();
+      return v;
+    }
+    return null;
+  }
+
+  /**
+   * Discovers whether the given file is in the Applet Archive
+   * 
+   * @param f
+   *          String
+   * @return boolean
+   */
+  private static boolean inArchive(Class<?> c, String f)
+  {
+    // This might throw a security exception in certain browsers
+    // Netscape Communicator for instance.
+    try
+    {
+      boolean rtn = (c.getResourceAsStream("/" + f) != null);
+      return rtn;
+    } catch (Exception ex)
+    {
+      System.out.println("Exception checking resources: " + f + " " + ex);
+      return false;
+    }
+  }
+
+  /**
+   * Allowing for a JavaScript function here.
+   */
+  void callInitCallback()
+  {
+    Object initjscallback = getParameterAsObject("oninit");
+    if (initjscallback != null)
+    {
+      SwingUtilities.invokeLater(new Runnable() {
+
+        @Override
+        public void run()
+        {
+          try
+          {
+            doSendCallback(initjscallback, new Object[] {this});
+          } catch (Exception e)
+          {
+            System.err.println("Exception when executing _oninit callback '"
+                    + initjscallback + "'.");
+            e.printStackTrace();
+          }
+        }
+        
+      });
+    }
+  }
+
+  /**
+   * Pass the provided array prepended with Jalview.this
+   * 
+   * Appropriated from org.jmol.appletjs.Jmol
+   * 
+   * @param callback
+   *          a window function or "alert"
+   * @param data
+   * @return String return from the callback method.
+   */
+  String doSendCallback(Object callback, Object[] data)
+  {
+    Jalview me = jalview;
+
+    if (me != null && callback != null)
+    {
+      /**
+       * @j2sNative
+       * 
+       *            try{
+       * 
+       *            if (callback == "alert") { alert(data[0]); return ""; } var
+       *            o; if (typeof callback == "function") { o = callback; } else
+       *            { if (!callback)return; var tokens = callback.split("."); o
+       *            = window[tokens[0]]; for (var i = 1; i < tokens.length; i++)
+       *            o = o[tokens[i]]; } var a = [me]; for (var i = 0; i <
+       *            data.length; i++) a.push(data[i] ? data[i].booleanValue &&
+       *            (data[i] = data[i].booleanValue()) : data[i]); return
+       *            o.apply(null,a) } catch (e) { System.out.println(callback +
+       *            " failed " + e); }
+       */
+    }
+    return "";
+  }
+
+  /**
+   * Initialize from Info.key/value pairs that match the old JalviewLite applet
+   * parameters.
+   * 
+   * See http://www.jalview.org/old/v2_8/examples/appletParameters.html
+   * 
+   * Note that some of these parameters are handled as command-line arguments,
+   * as determined in ArgsParser.
+   * 
+   * @param alf
+   */
+  void initFromParams(AlignFrame alf)
+  {
+    String sep = getParameter("separator");
+    if (sep != null && sep.length() > 0)
+    {
+      separator = sep;
+    }
+    initTree(alf);
+    initScoreFile(alf);
+    initFeatures(alf);
+    initAnnotations(alf);
+    initJnetFile(alf);
+    initPdbFiles(alf);
+  }
+
+  /**
+   * Load annotations if specified by parameter. Returns true if loaded, else
+   * false.
+   * 
+   * 
+   * @param alignFrame
+   * @return
+   */
+  private boolean initAnnotations(AlignFrame alf)
+  {
+
+    String param = getParameter("annotations");
+    if (param == null)
+      return false;
+    ret[0] = param;
+    DataSourceType protocol = resolveFileProtocol(ret);
+    param = ret[0];
+    if (!new AnnotationFile().annotateAlignmentView(alf.getViewport(),
+            param, protocol))
+    {
+      System.err.println("Annotations were not added from annotation file '"
+              + param + "'");
+      return false;
+    }
+    updateForAnnotations();
+    return true;
+  }
+
+  /**
+   * Load features file and view settings as specified by parameters. Returns
+   * true if features were loaded, else false.
+   * 
+   * @param
+   * 
+   * @param alignFrame
+   * @return
+   */
+  private boolean initFeatures(AlignFrame alf)
+  {
+
+    // ///////////////////////////
+    // modify display of features
+    // we do this before any features have been loaded, ensuring any hidden
+    // groups are hidden when features first displayed
+    //
+    // hide specific groups
+    //
+    String param = getParameter("hidefeaturegroups");
+    if (param != null)
+    {
+      setFeatureGroupState(separatorListToArray(param, separator),
+              false, alf);
+      // setFeatureGroupStateOn(newAlignFrame, param, false);
+    }
+    // show specific groups
+    param = getParameter("showfeaturegroups");
+    if (param != null)
+    {
+      setFeatureGroupState(separatorListToArray(param, separator),
+              true, alf);
+      // setFeatureGroupStateOn(newAlignFrame, param, true);
+    }
+    // and now load features
+    param = getParameter("features");
+    if (param == null)
+    {
+      return false;
+    }
+    if (!parseFeaturesFile(param, alf))
+      return false;
+    param = getParameter("showFeatureSettings");
+    if (param != null && param.equalsIgnoreCase("true"))
+    {
+      alf.showFeatureSettingsUI();
+    }
+    return true;
+  }
+
+  /**
+   * Load in a Jnetfile if specified by parameter. Returns true if loaded, else
+   * false.
+   * 
+   * @param alignFrame
+   * @return
+   */
+  private boolean initJnetFile(AlignFrame alf)
+  {
+
+    String param = getParameter("jnetfile");
+    if (param == null)
+    {
+      // jnet became jpred around 2016
+      param = getParameter("jpredfile");
+    }
+    if (param != null)
+    {
+      try
+      {
+        ret[0] = param;
+        DataSourceType protocol = resolveFileProtocol(ret);
+        JPredFile predictions = new JPredFile(ret[0], protocol);
+        JnetAnnotationMaker.add_annotation(predictions,
+                alf.getViewport().getAlignment(), 0, false);
+        // false == do not add sequence profile from concise output
+        alf.getViewport().getAlignment().setupJPredAlignment();
+        updateForAnnotations();
+      } catch (Exception ex)
+      {
+        ex.printStackTrace();
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Load PDBFiles if any specified by parameter(s). Returns true if loaded,
+   * else false.
+   * 
+   * @param loaderFrame
+   * @return
+   */
+  private boolean initPdbFiles(AlignFrame alf)
+  {
+
+    /*
+     * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6 -
+     * related to JAL-434
+     */
+
+    // not supported (as for JalviewLite)
+    // boolean doAlign = false;//"true".equalsIgnoreCase("" +
+    // getAppletParameter("alignpdbfiles", false));
+    // setAlignPdbStructures(doAlign);
+    /*
+     * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
+     * PDB|1GAQ|1GAQ|C">
+     * 
+     * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
+     * 
+     * <param name="PDBfile3" value="1q0o Q45135_9MICO">
+     */
+
+    // Accumulate pdbs here if they are heading for the same view (if
+    // alignPdbStructures is true)
+    // ArrayList<Object[]> pdbs = new ArrayList<>();
+    // init a lazy matcher if we're asked to
+    boolean relaxed = "true"
+            .equalsIgnoreCase(getParameter("relaxedidmatch"));
+    jalview.analysis.SequenceIdMatcher matcher = relaxed
+            ? new jalview.analysis.SequenceIdMatcher(
+                    alf.getViewport().getAlignment().getSequencesArray())
+            : null;
+
+    String param = getParameter("PDBFILE");
+    int plast = (param == null ? 9 : 1);
+    if (param == null && (param = getParameter("PDBFILE1")) == null)
+    {
+      return false;
+    }
+    for (int p = 1; p <= plast; p++)
+    {
+      if (p > 1)
+      {
+        param = getParameter("PDBFILE" + p);
+        if (param == null)
+          break;
+      }
+      PDBEntry pdb = new PDBEntry();
+
+      String seqstring;
+      SequenceI[] seqs = null;
+      String[] chains = null;
+
+      StringTokenizer st = new StringTokenizer(param, " ");
+
+      if (st.countTokens() < 2)
+      {
+        String sequence = getParameter("PDBSEQ");
+        if (sequence != null)
+        {
+          seqs = new SequenceI[] { matcher == null
+                  ? (Sequence) alf.getViewport().getAlignment()
+                          .findName(sequence)
+                  : matcher.findIdMatch(sequence) };
+        }
+
+      }
+      else
+      {
+        param = st.nextToken();
+        List<SequenceI> tmp = new ArrayList<>();
+        List<String> tmp2 = new ArrayList<>();
+
+        while (st.hasMoreTokens())
+        {
+          seqstring = st.nextToken();
+          StringTokenizer st2 = new StringTokenizer(seqstring, "=");
+          if (st2.countTokens() > 1)
+          {
+            // This is the chain
+            tmp2.add(st2.nextToken());
+            seqstring = st2.nextToken();
+          }
+          tmp.add(matcher == null
+                  ? (Sequence) alf.getViewport().getAlignment()
+                          .findName(seqstring)
+                  : matcher.findIdMatch(seqstring));
+        }
+
+        seqs = tmp.toArray(new SequenceI[tmp.size()]);
+        if (tmp2.size() == tmp.size())
+        {
+          chains = tmp2.toArray(new String[tmp2.size()]);
+        }
+      }
+      pdb.setId(param);
+      ret[0] = param;
+      DataSourceType protocol = resolveFileProtocol(ret);
+      // TODO check JAL-357 for files in a jar (CLASSLOADER)
+      pdb.setFile(ret[0]);
+
+      if (seqs != null)
+      {
+        for (int i = 0; i < seqs.length; i++)
+        {
+          if (seqs[i] != null)
+          {
+            ((Sequence) seqs[i]).addPDBId(pdb);
+            StructureSelectionManager
+                    .getStructureSelectionManager(
+                            (StructureSelectionManagerProvider) this)
+                    .registerPDBEntry(pdb);
+          }
+        }
+
+        // if (doAlign)
+        // {
+        // pdbs.add(new Object[] { pdb, seqs, chains, protocol });
+        // }
+        // else
+        {
+          StructureViewer.launchStructureViewer(
+                  (alf == null ? getCurrentAlignFrame() : alf).alignPanel,
+                  pdb, seqs);
+        }
+      }
+    }
+    //
+    // if (doAlign && pdbs.size() > 0)
+    // {
+    // SequenceI[][] seqs = new SequenceI[pdbs.size()][];
+    // PDBEntry[] pdb = new PDBEntry[pdbs.size()];
+    // String[][] chains = new String[pdbs.size()][];
+    // String[] protocols = new String[pdbs.size()];
+    // for (int pdbsi = 0, pdbsiSize = pdbs
+    // .size(); pdbsi < pdbsiSize; pdbsi++)
+    // {
+    // Object[] o = pdbs.get(pdbsi);
+    // pdb[pdbsi] = (PDBEntry) o[0];
+    // seqs[pdbsi] = (SequenceI[]) o[1];
+    // chains[pdbsi] = (String[]) o[2];
+    // protocols[pdbsi] = (String) o[3];
+    // }
+    //// alignedStructureView(pdb, seqs, chains, protocols);
+    // result = true;
+    // }
+    return true;
+  }
+
+  /**
+   * Load a score file if specified by parameter. Returns true if file was
+   * loaded, else false.
+   * 
+   * @param loaderFrame
+   */
+  private boolean initScoreFile(AlignFrame alf)
+  {
+
+    String sScoreFile = getParameter("scoreFile");
+    if (sScoreFile != null && !"".equals(sScoreFile))
+    {
+      try
+      {
+        if (loadScoreFile(sScoreFile, alf))
+        {
+          return true;
+        }
+        System.err.println(
+                "Failed to parse T-COFFEE parameter as a valid score file ('"
+                        + sScoreFile + "')");
+      } catch (Exception e)
+      {
+        System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
+                sScoreFile, e.getMessage());
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Load a tree for the alignment if specified by parameter. Returns true if a
+   * tree was loaded, else false.
+   * 
+   * @return
+   */
+  private boolean initTree(AlignFrame alf)
+  {
+    String treeFile;
+    if ((treeFile = getParameter("tree")) == null
+            && (treeFile = getParameter("treefile")) == null)
+      return false;
+    if (alf == null)
+      alf = getCurrentAlignFrame();
+    try
+    {
+      ret[0] = treeFile;
+      NewickFile nf = new NewickFile(treeFile, resolveFileProtocol(ret));
+      nf.parse();
+      if (nf.getTree() != null)
+      {
+        treeFile = ret[0];
+        alf.getViewport()
+                .setCurrentTree(alf.showNewickTree(nf, treeFile).getTree());
+        return true;
+      }
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
+    }
+    return false;
+  }
+
+  private void updateForAnnotations()
+  {
+    getCurrentAlignFrame().updateForAnnotations();
+  }
+}
index e7f2a53..6a8db59 100644 (file)
@@ -45,10 +45,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;
@@ -920,7 +922,7 @@ public class JalviewLite extends Applet
     setMouseoverListener(currentAlignFrame, listener);
   }
 
-  private Vector<jalview.javascript.JSFunctionExec> javascriptListeners = new Vector<>();
+  private Vector<JSFunctionExec> javascriptListeners = new Vector<>();
 
   /*
    * (non-Javadoc)
@@ -942,7 +944,7 @@ public class JalviewLite extends Applet
         return;
       }
     }
-    jalview.javascript.MouseOverListener mol = new jalview.javascript.MouseOverListener(
+    MouseOverListener mol = new MouseOverListener(
             this, af, listener);
     javascriptListeners.addElement(mol);
     StructureSelectionManager.getStructureSelectionManager(this)
@@ -989,7 +991,7 @@ public class JalviewLite extends Applet
         return;
       }
     }
-    jalview.javascript.JsSelectionSender mol = new jalview.javascript.JsSelectionSender(
+    JsSelectionSender mol = new JsSelectionSender(
             this, af, listener);
     javascriptListeners.addElement(mol);
     StructureSelectionManager.getStructureSelectionManager(this)
@@ -1128,7 +1130,7 @@ public class JalviewLite extends Applet
     {
       while (javascriptListeners.size() > 0)
       {
-        jalview.javascript.JSFunctionExec mol = javascriptListeners
+        JSFunctionExec mol = javascriptListeners
                 .elementAt(0);
         javascriptListeners.removeElement(mol);
         if (mol instanceof SelectionListener)
@@ -1155,7 +1157,7 @@ public class JalviewLite extends Applet
     StructureSelectionManager.release(this);
   }
 
-  private jalview.javascript.JSFunctionExec jsFunctionExec;
+  private JSFunctionExec jsFunctionExec;
 
   /*
    * (non-Javadoc)
@@ -1230,7 +1232,7 @@ public class JalviewLite extends Applet
    * (non-Javadoc)
    * 
    * @see
-   * jalview.javascript.JalviewLiteJsApi#scrollViewToRowIn(jalview.appletgui
+   * JalviewLiteJsApi#scrollViewToRowIn(jalview.appletgui
    * .AlignFrame, java.lang.String)
    */
   @Override
@@ -1261,7 +1263,7 @@ public class JalviewLite extends Applet
    * (non-Javadoc)
    * 
    * @see
-   * jalview.javascript.JalviewLiteJsApi#scrollViewToColumnIn(jalview.appletgui
+   * JalviewLiteJsApi#scrollViewToColumnIn(jalview.appletgui
    * .AlignFrame, java.lang.String)
    */
   @Override
index c4098e2..8d81a34 100755 (executable)
@@ -591,7 +591,8 @@ public class Alignment implements AlignmentI, AutoCloseable
   @Override
   public SequenceI findName(SequenceI startAfter, String token, boolean b)
   {
-
+    if (token == null)
+      return null;
     int i = 0;
     SequenceI sq = null;
     String sqname = null;
index c1dc77c..d39e9f1 100755 (executable)
@@ -21,6 +21,7 @@
 package jalview.datamodel;
 
 import jalview.util.CaseInsensitiveString;
+import jalview.ws.params.InvalidArgumentException;
 
 import java.util.Collections;
 import java.util.Enumeration;
@@ -152,6 +153,32 @@ public class PDBEntry
   {
   }
 
+  /**
+   * 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)
   {
@@ -253,6 +280,12 @@ public class PDBEntry
     return id;
   }
 
+  /**
+   * TODO 
+   * 
+   * @param key  "protocol" 
+   * @param value
+   */
   public void setProperty(String key, Object value)
   {
     if (this.properties == null)
index bcf404b..a57fd55 100644 (file)
@@ -29,17 +29,29 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.TreeMap;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
+
 /**
  * A singleton class to hold the set of attributes known for each feature type
  */
-public class FeatureAttributes
+public class FeatureAttributes implements ApplicationSingletonI
 {
   public enum Datatype
   {
     Character, Number, Mixed
   }
 
-  private static FeatureAttributes instance = new FeatureAttributes();
+  public static FeatureAttributes getInstance()
+  {
+    return (FeatureAttributes) ApplicationSingletonProvider
+            .getInstance(FeatureAttributes.class);
+  }
+
+  private FeatureAttributes()
+  {
+    attributes = new HashMap<>();
+  }
 
   /*
    * map, by feature type, of a map, by attribute name, of
@@ -120,12 +132,17 @@ public class FeatureAttributes
       if (value != null)
       {
         value = value.trim();
+        if (value.isEmpty())
+        {
+          return;
+        }
 
         /*
          * Parse numeric value unless we have previously
          * seen text data for this attribute type
          */
-        if (type == null || type == Datatype.Number)
+        if ((type == null && couldBeNumber(value))
+                || type == Datatype.Number)
         {
           try
           {
@@ -149,11 +166,23 @@ public class FeatureAttributes
             hasValue = false;
           }
         }
+        else
+        {
+          /*
+           * if not a number, and not seen before...
+           */
+          type = Datatype.Character;
+          min = 0f;
+          max = 0f;
+          hasValue = false;
+        }
       }
     }
 
     /**
-     * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded
+     * Answers the description of the attribute, if recorded and unique, or null
+     * if either no, or more than description is recorded
+     * 
      * @return
      */
     public String getDescription()
@@ -193,21 +222,6 @@ public class FeatureAttributes
   }
 
   /**
-   * Answers the singleton instance of this class
-   * 
-   * @return
-   */
-  public static FeatureAttributes getInstance()
-  {
-    return instance;
-  }
-
-  private FeatureAttributes()
-  {
-    attributes = new HashMap<>();
-  }
-
-  /**
    * Answers the attribute names known for the given feature type, in
    * alphabetical order (not case sensitive), or an empty set if no attributes
    * are known. An attribute name is typically 'simple' e.g. "AC", but may be
@@ -227,6 +241,34 @@ public class FeatureAttributes
   }
 
   /**
+   * A partial check that the string is numeric - only checks the first
+   * character. Returns true if the first character is a digit, or if it is '.',
+   * '+' or '-' and not the only character. Otherwise returns false (including
+   * for an empty string). Note this is not a complete check as it returns true
+   * for (e.g.) "1A".
+   * 
+   * @param f
+   * @return
+   */
+  public static boolean couldBeNumber(String f)
+  {
+    int len = f.length();
+    if (len == 0)
+    {
+      return false;
+    }
+    char ch = f.charAt(0);
+    switch (ch)
+    {
+    case '.':
+    case '+':
+    case '-':
+      return len > 1;
+    }
+    return (ch <= '9' && ch >= '0');
+  }
+
+  /**
    * Answers true if at least one attribute is known for the given feature type,
    * else false
    * 
@@ -354,7 +396,7 @@ public class FeatureAttributes
     {
       return;
     }
-  
+
     Map<String[], AttributeData> atts = attributes.get(featureType);
     if (atts == null)
     {
index b316821..4a87349 100644 (file)
@@ -23,6 +23,9 @@ package jalview.datamodel.features;
 import java.util.HashMap;
 import java.util.Map;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
+
 /**
  * A singleton to hold metadata about feature attributes, keyed by a unique
  * feature source identifier
@@ -30,22 +33,17 @@ import java.util.Map;
  * @author gmcarstairs
  *
  */
-public class FeatureSources
+public class FeatureSources implements ApplicationSingletonI
 {
-  private static FeatureSources instance = new FeatureSources();
-
-  private Map<String, FeatureSourceI> sources;
 
-  /**
-   * Answers the singleton instance of this class
-   * 
-   * @return
-   */
   public static FeatureSources getInstance()
   {
-    return instance;
+    return (FeatureSources) ApplicationSingletonProvider
+            .getInstance(FeatureSources.class);
   }
 
+  private Map<String, FeatureSourceI> sources;
+
   private FeatureSources()
   {
     sources = new HashMap<>();
index 8ac4991..82772bc 100644 (file)
@@ -372,7 +372,7 @@ public class SequenceFeatures implements SequenceFeaturesI
     {
       return true;
     }
-    SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+    SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology();
     for (String term : soTerm)
     {
       if (type.equals(term) || so.isA(type, term))
index 8f13d99..bf37265 100644 (file)
@@ -93,7 +93,7 @@ public class EnsemblCds extends EnsemblSeqProxy
   @Override
   protected boolean retainFeature(SequenceFeature sf, String accessionId)
   {
-    if (SequenceOntologyFactory.getInstance().isA(sf.getType(),
+    if (SequenceOntologyFactory.getSequenceOntology().isA(sf.getType(),
             SequenceOntologyI.CDS))
     {
       return false;
index 157b8b9..3d957f0 100644 (file)
@@ -578,7 +578,7 @@ public class EnsemblGene extends EnsemblSeqProxy
   @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))
     {
@@ -625,7 +625,7 @@ public class EnsemblGene extends EnsemblSeqProxy
   {
     return new FeatureSettingsAdapter()
     {
-      SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+      SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology();
 
       @Override
       public boolean isFeatureDisplayed(String type)
index fd8800f..11f8b7b 100644 (file)
@@ -732,7 +732,7 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient
        * for sequence_variant on reverse strand, have to convert the allele
        * values to their complements
        */
-      if (!forwardStrand && SequenceOntologyFactory.getInstance()
+      if (!forwardStrand && SequenceOntologyFactory.getSequenceOntology()
               .isA(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT))
       {
         reverseComplementAlleles(copy);
@@ -966,7 +966,7 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient
   public static boolean isTranscript(String featureType)
   {
     return SequenceOntologyI.NMD_TRANSCRIPT_VARIANT.equals(featureType)
-            || SequenceOntologyFactory.getInstance().isA(featureType,
+            || SequenceOntologyFactory.getSequenceOntology().isA(featureType,
                     SequenceOntologyI.TRANSCRIPT);
   }
 }
index 453152e..cfb30b3 100644 (file)
@@ -1368,7 +1368,9 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
             htmlName + ((Object) this).toString(), documentBase, codeBase,
             commandOptions, this);
 
-    viewer.setJmolStatusListener(this); // extends JmolCallbackListener
+    viewer.setJmolStatusListener(this); 
+    
+    // BH how about extending Jmol's status listener? 
 
     try
     {
index 0d631e6..89b2415 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.ext.so;
 
-import jalview.io.gff.SequenceOntologyI;
-
 import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -44,6 +42,9 @@ import org.biojava.nbio.ontology.Triple;
 import org.biojava.nbio.ontology.io.OboParser;
 import org.biojava.nbio.ontology.utils.Annotation;
 
+import jalview.bin.Cache;
+import jalview.io.gff.SequenceOntologyI;
+
 /**
  * A wrapper class that parses the Sequence Ontology and exposes useful access
  * methods. This version uses the BioJava parser.
@@ -183,19 +184,19 @@ public class SequenceOntology implements SequenceOntologyI
             boolean oldTermIsObsolete = isObsolete(replaced);
             if (newTermIsObsolete && !oldTermIsObsolete)
             {
-              System.err.println("Ignoring " + term.getName()
+              Cache.log.debug("Ignoring " + term.getName()
                       + " as obsolete and duplicated by "
                       + replaced.getName());
               term = replaced;
             }
             else if (!newTermIsObsolete && oldTermIsObsolete)
             {
-              System.err.println("Ignoring " + replaced.getName()
+              Cache.log.debug("Ignoring " + replaced.getName()
                       + " as obsolete and duplicated by " + term.getName());
             }
             else
             {
-              System.err.println("Warning: " + term.getName()
+              Cache.log.debug("Warning: " + term.getName()
                       + " has replaced " + replaced.getName()
                       + " for lookup of '" + description + "'");
             }
index 22ed591..c6860cc 100644 (file)
@@ -37,6 +37,8 @@ import com.sun.jersey.api.client.ClientResponse;
 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;
@@ -53,13 +55,16 @@ import jalview.util.Platform;
  * 
  * @author tcnofoegbu
  */
-public class PDBFTSRestClient extends FTSRestClient
+public class PDBFTSRestClient extends FTSRestClient implements 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()
   {
   }
@@ -458,15 +463,6 @@ public static String parseJsonExceptionString(String jsonErrorResponse)
     return "/fts/pdb_data_columns.txt";
   }
 
-  public static FTSRestClientI getInstance()
-  {
-    if (instance == null)
-    {
-      instance = new PDBFTSRestClient();
-    }
-    return instance;
-  }
-
   private Collection<FTSDataColumnI> allDefaultDisplayedStructureDataColumns;
 
   public Collection<FTSDataColumnI> getAllDefaultDisplayedStructureDataColumns()
index 3f0b8a4..0d860c4 100644 (file)
@@ -21,6 +21,8 @@
 
 package jalview.fts.service.uniprot;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.bin.Cache;
 import jalview.fts.api.FTSData;
 import jalview.fts.api.FTSDataColumnI;
@@ -44,7 +46,15 @@ import com.sun.jersey.api.client.WebResource;
 import com.sun.jersey.api.client.config.DefaultClientConfig;
 
 public class UniProtFTSRestClient extends FTSRestClient
+implements ApplicationSingletonI
 {
+
+public static FTSRestClientI getInstance()
+{
+return (FTSRestClientI) ApplicationSingletonProvider
+    .getInstance(UniProtFTSRestClient.class);
+}
+
   private static final String DEFAULT_UNIPROT_DOMAIN = "https://www.uniprot.org";
 
   static
@@ -52,11 +62,9 @@ public class UniProtFTSRestClient extends FTSRestClient
     Platform.addJ2SDirectDatabaseCall(DEFAULT_UNIPROT_DOMAIN);
   }
 
-  private static FTSRestClientI instance = null;
-
   public final String uniprotSearchEndpoint;
 
-  public UniProtFTSRestClient()
+  private UniProtFTSRestClient()
   {
     super();
     uniprotSearchEndpoint = Cache.getDefault("UNIPROT_DOMAIN",
@@ -347,15 +355,6 @@ public class UniProtFTSRestClient extends FTSRestClient
     };
   }
 
-  public static FTSRestClientI getInstance()
-  {
-    if (instance == null)
-    {
-      instance = new UniProtFTSRestClient();
-    }
-    return instance;
-  }
-
   @Override
   public String getColumnDataConfigFileName()
   {
index 70601c9..3a8fb7c 100644 (file)
@@ -96,7 +96,7 @@ public class AlignExportOptions extends JPanel
     this.settings = defaults;
     this.isComplexAlignFile = format.isComplexAlignFile();
     init(viewport.hasHiddenRows(), viewport.hasHiddenColumns());
-    dialog = JvOptionPane.newOptionDialog(Desktop.desktop);
+    dialog = JvOptionPane.newOptionDialog(Desktop.getDesktopPane());
   }
 
   /**
index 29f6a2e..14e09e7 100644 (file)
  */
 package jalview.gui;
 
-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.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.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 ext.vamsas.ServiceHandle;
 import jalview.analysis.AlignmentSorter;
 import jalview.analysis.AlignmentUtils;
 import jalview.analysis.CrossRef;
@@ -156,6 +103,60 @@ import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
 import jalview.ws.seqfetcher.DbSourceProxy;
 
+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.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.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 ext.vamsas.ServiceHandle;
+
 /**
  * DOCUMENT ME!
  * 
@@ -167,6 +168,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         IProgressIndicator, AlignViewControllerGuiI, ColourChangeListener
 {
 
+  public static int frameCount;
+
   public static final int DEFAULT_WIDTH = 700;
 
   public static final int DEFAULT_HEIGHT = 500;
@@ -194,6 +197,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
   File fileObject;
 
+  private int id;
+
   /**
    * Creates a new AlignFrame object with specific width and height.
    * 
@@ -288,6 +293,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns, int width,
           int height, String sequenceSetId, String viewId)
   {
+
+    id = (++frameCount);
+
     setSize(width, height);
 
     if (al.getDataset() == null)
@@ -297,9 +305,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     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();
   }
 
@@ -319,8 +327,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       viewport.hideSequence(hiddenSeqs);
     }
-    alignPanel = new AlignmentPanel(this, viewport);
-    addAlignmentPanel(alignPanel, true);
+    // alignPanel = new AlignmentPanel(this, viewport);
+    // addAlignmentPanel(alignPanel, true);
     init();
   }
 
@@ -336,7 +344,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     viewport = ap.av;
     alignPanel = ap;
-    addAlignmentPanel(ap, false);
+    // addAlignmentPanel(ap, false);
     init();
   }
 
@@ -344,13 +352,39 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 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,
@@ -365,7 +399,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       // 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"))
@@ -377,8 +411,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       sortPairwiseMenuItem_actionPerformed(null);
     }
 
-    this.alignPanel.av
-            .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
+    // BH see above
+    //
+    // this.alignPanel.av
+    // .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
 
     setMenusFromViewport(viewport);
     buildSortByAnnotationScoresMenu();
@@ -393,7 +429,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     });
     buildColourMenu();
 
-    if (Desktop.desktop != null)
+    if (Desktop.getDesktopPane() != null)
     {
       this.setDropTarget(new java.awt.dnd.DropTarget(this, this));
       if (!Platform.isJS())
@@ -408,7 +444,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       wrapMenuItem_actionPerformed(null);
     }
 
-    if (jalview.bin.Cache.getDefault("SHOW_OVERVIEW", false))
+    if (jalview.bin.Cache.getDefault(Preferences.SHOW_OVERVIEW, false))
     {
       this.overviewMenuItem_actionPerformed(null);
     }
@@ -492,6 +528,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
     addFocusListener(new FocusAdapter()
     {
+
       @Override
       public void focusGained(FocusEvent e)
       {
@@ -510,6 +547,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param format
    *          format of file
    */
+
   public void setFileName(String file, FileFormatI format)
   {
     fileName = file;
@@ -523,6 +561,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param file
    */
+
   public void setFileObject(File file)
   {
     this.fileObject = file;
@@ -532,10 +571,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * Add a KeyListener with handlers for various KeyPressed and KeyReleased
    * events
    */
+
   void addKeyListener()
   {
     addKeyListener(new KeyAdapter()
     {
+
       @Override
       public void keyPressed(KeyEvent evt)
       {
@@ -552,8 +593,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         switch (evt.getKeyCode())
         {
 
-        case 27: // escape key
-          deselectAllSequenceMenuItem_actionPerformed(null);
+        case KeyEvent.VK_ESCAPE: // escape key
+                                 // alignPanel.deselectAllSequences();
+          alignPanel.deselectAllSequences();
 
           break;
 
@@ -736,16 +778,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         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;
         }
@@ -791,9 +831,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
         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);
+      }
     }
   }
 
@@ -816,9 +859,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   private void addServiceListeners()
   {
     final java.beans.PropertyChangeListener thisListener;
-    Desktop.instance.addJalviewPropertyChangeListener("services",
+    Desktop.getInstance().addJalviewPropertyChangeListener("services",
             thisListener = new java.beans.PropertyChangeListener()
             {
+
               @Override
               public void propertyChange(PropertyChangeEvent evt)
               {
@@ -842,19 +886,21 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             });
     addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
     {
+
       @Override
       public void internalFrameClosed(
               javax.swing.event.InternalFrameEvent evt)
       {
         // System.out.println("deregistering discoverer listener");
-        Desktop.instance.removeJalviewPropertyChangeListener("services",
-                thisListener);
+        Desktop.getInstance().removeJalviewPropertyChangeListener(
+                "services", thisListener);
         closeMenuItem_actionPerformed(true);
       }
     });
     // Finally, build the menu once to get current service state
     new Thread(new Runnable()
     {
+
       @Override
       public void run()
       {
@@ -867,6 +913,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * Configure menu items that vary according to whether the alignment is
    * nucleotide or protein
    */
+
   public void setGUINucleotide()
   {
     AlignmentI al = getViewport().getAlignment();
@@ -891,6 +938,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 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()
   {
@@ -904,6 +952,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param av
    *          AlignViewport
    */
+
   public void setMenusFromViewport(AlignViewport av)
   {
     padGapsMenuitem.setSelected(av.isPadGaps());
@@ -921,13 +970,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     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());
@@ -945,7 +989,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     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);
 
@@ -960,6 +1005,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param b
    */
+
   public void setGroovyEnabled(boolean b)
   {
     runGroovy.setEnabled(b);
@@ -972,6 +1018,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
    */
+
   @Override
   public void setProgressBar(String message, long id)
   {
@@ -989,6 +1036,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @return true if any progress bars are still active
    */
+
   @Override
   public boolean operationInProgress()
   {
@@ -1000,6 +1048,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * will cause the status bar to be hidden, with possibly undesirable flicker
    * of the screen layout.
    */
+
   @Override
   public void setStatus(String text)
   {
@@ -1009,6 +1058,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /*
    * Added so Castor Mapping file can obtain Jalview Version
    */
+
   public String getVersion()
   {
     return jalview.bin.Cache.getProperty("VERSION");
@@ -1028,103 +1078,106 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void addFromFile_actionPerformed(ActionEvent e)
   {
-    Desktop.instance.inputLocalFileMenuItem_actionPerformed(viewport);
+    Desktop.getInstance().inputLocalFileMenuItem_actionPerformed(viewport);
   }
 
   @Override
   public void reload_actionPerformed(ActionEvent e)
   {
-    if (fileName != null)
+    if (fileName == null)
     {
-      // 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))
+      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++)
       {
-        JInternalFrame[] frames = Desktop.desktop.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))
         {
-          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.getInstance().closeAssociatedWindows();
+          } catch (java.beans.PropertyVetoException ex)
           {
-            try
-            {
-              frames[i].setSelected(true);
-              Desktop.instance.closeAssociatedWindows();
-            } catch (java.beans.PropertyVetoException ex)
-            {
-            }
           }
-
         }
-        Desktop.instance.closeAssociatedWindows();
 
-        FileLoader loader = new FileLoader();
-        DataSourceType protocol = fileName.startsWith("http:")
-                ? DataSourceType.URL
-                : DataSourceType.FILE;
-        loader.LoadFile(viewport, fileName, protocol, currentFileFormat);
       }
-      else
-      {
-        Rectangle bounds = this.getBounds();
+      Desktop.getInstance().closeAssociatedWindows();
 
-        FileLoader loader = new FileLoader();
+      FileLoader loader = new FileLoader();
+      DataSourceType protocol = fileName.startsWith("http:")
+              ? DataSourceType.URL
+              : DataSourceType.FILE;
+      loader.LoadFile(viewport, fileName, protocol, currentFileFormat);
+    }
+    else
+    {
+      Rectangle bounds = this.getBounds();
 
-        AlignFrame newframe = null;
+      FileLoader loader = new FileLoader();
 
-        if (fileObject == null)
-        {
+      AlignFrame newframe = null;
 
-          DataSourceType protocol = (fileName.startsWith("http:")
-                  ? DataSourceType.URL
-                  : DataSourceType.FILE);
-          newframe = loader.LoadFileWaitTillLoaded(fileName, protocol,
-                  currentFileFormat);
-        }
-        else
-        {
-          newframe = loader.LoadFileWaitTillLoaded(fileObject,
-                  DataSourceType.FILE, currentFileFormat);
-        }
+      if (fileObject == null)
+      {
+
+        DataSourceType protocol = (fileName.startsWith("http:")
+                ? DataSourceType.URL
+                : DataSourceType.FILE);
+        newframe = loader.LoadFileWaitTillLoaded(fileName, protocol,
+                currentFileFormat);
+      }
+      else
+      {
+        newframe = loader.LoadFileWaitTillLoaded(fileObject,
+                DataSourceType.FILE, currentFileFormat);
+      }
 
-        newframe.setBounds(bounds);
-        if (featureSettings != null && featureSettings.isShowing())
+      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
@@ -1145,6 +1198,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 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()
   {
@@ -1168,7 +1222,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // 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"),
@@ -1199,6 +1253,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    *
    * @return true if last call to saveAlignment(file, format) was successful.
    */
+
   public boolean isSaveAlignmentSuccessful()
   {
 
@@ -1231,6 +1286,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param file
    * @param format
    */
+
   public void saveAlignment(String file, FileFormatI format)
   {
     lastSaveSuccessful = true;
@@ -1242,21 +1298,23 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       String shortName = title;
       if (shortName.indexOf(File.separatorChar) > -1)
       {
-        shortName = shortName.substring(
-                shortName.lastIndexOf(File.separatorChar) + 1);
+        shortName = shortName
+                .substring(shortName.lastIndexOf(File.separatorChar) + 1);
       }
-      lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file, shortName);
-      
+      lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file,
+              shortName);
+
       statusBar.setText(MessageManager.formatMessage(
               "label.successfully_saved_to_file_in_format", new Object[]
               { fileName, format }));
-      
+
       return;
     }
 
     AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
     Runnable cancelAction = new Runnable()
     {
+
       @Override
       public void run()
       {
@@ -1265,6 +1323,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     };
     Runnable outputAction = new Runnable()
     {
+
       @Override
       public void run()
       {
@@ -1287,16 +1346,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           BackupFiles backupfiles = doBackup ? new BackupFiles(file) : null;
           try
           {
-            String tempFilePath = doBackup ? backupfiles.getTempFilePath() : file;
-                       PrintWriter out = new PrintWriter(
-                    new FileWriter(tempFilePath));
+            String tempFilePath = doBackup ? backupfiles.getTempFilePath()
+                    : file;
+            PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
 
             out.print(output);
             out.close();
             AlignFrame.this.setTitle(file);
             statusBar.setText(MessageManager.formatMessage(
-                  "label.successfully_saved_to_file_in_format", new Object[]
-                  { fileName, format.getName() }));
+                    "label.successfully_saved_to_file_in_format",
+                    new Object[]
+                    { fileName, format.getName() }));
             lastSaveSuccessful = true;
           } catch (Exception ex)
           {
@@ -1338,6 +1398,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param fileFormatName
    */
+
   @Override
   protected void outputText_actionPerformed(String fileFormatName)
   {
@@ -1346,6 +1407,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
     Runnable outputAction = new Runnable()
     {
+
       @Override
       public void run()
       {
@@ -1396,6 +1458,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void htmlMenuItem_actionPerformed(ActionEvent e)
   {
@@ -1410,6 +1473,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     bjs.exportHTML(null);
   }
 
+  // ??
+
   public void createImageMap(File file, String image)
   {
     alignPanel.makePNGImageMap(file, image);
@@ -1421,6 +1486,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param f
    */
+
   @Override
   public void createPNG(File f)
   {
@@ -1433,6 +1499,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param f
    */
+
   @Override
   public void createEPS(File f)
   {
@@ -1445,6 +1512,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param f
    */
+
   @Override
   public void createSVG(File f)
   {
@@ -1464,6 +1532,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void printMenuItem_actionPerformed(ActionEvent e)
   {
@@ -1490,11 +1559,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     final JalviewFileChooser chooser = new JalviewFileChooser(
             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
     chooser.setFileView(new JalviewFileView());
-    String tooltip = MessageManager.getString("label.load_jalview_annotations");
+    String tooltip = MessageManager
+            .getString("label.load_jalview_annotations");
     chooser.setDialogTitle(tooltip);
     chooser.setToolTipText(tooltip);
     chooser.setResponseHandler(0, new Runnable()
     {
+
       @Override
       public void run()
       {
@@ -1513,6 +1584,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param closeAllTabs
    */
+
   @Override
   public void closeMenuItem_actionPerformed(boolean closeAllTabs)
   {
@@ -1567,6 +1639,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param panelToClose
    */
+
   public void closeView(AlignmentPanel panelToClose)
   {
     int index = tabbedPane.getSelectedIndex();
@@ -1590,6 +1663,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * DOCUMENT ME!
    */
+
   void updateEditMenuBar()
   {
 
@@ -1643,6 +1717,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @return alignment objects for all views
    */
+
   AlignmentI[] getViewAlignments()
   {
     if (alignPanels != null)
@@ -1668,6 +1743,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void undoMenuItem_actionPerformed(ActionEvent e)
   {
@@ -1695,8 +1771,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       // && viewport.getColumnSelection().getHiddenColumns() != null &&
       // viewport.getColumnSelection()
       // .getHiddenColumns().size() > 0);
-      originalSource.firePropertyChange("alignment", null,
-              originalSource.getAlignment().getSequences());
+      originalSource.notifyAlignment();
+
     }
   }
 
@@ -1706,6 +1782,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void redoMenuItem_actionPerformed(ActionEvent e)
   {
@@ -1735,8 +1812,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       // && viewport.getColumnSelection().getHiddenColumns() != null &&
       // viewport.getColumnSelection()
       // .getHiddenColumns().size() > 0);
-      originalSource.firePropertyChange("alignment", null,
-              originalSource.getAlignment().getSequences());
+      originalSource.notifyAlignment();
+
     }
   }
 
@@ -1788,6 +1865,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param up
    *          DOCUMENT ME!
    */
+
   public void moveSelectedSequences(boolean up)
   {
     SequenceGroup sg = viewport.getSelectionGroup();
@@ -1912,6 +1990,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void copy_actionPerformed()
   {
@@ -1934,16 +2013,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     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.instance);
+              Desktop.getInstance());
     } catch (OutOfMemoryError er)
     {
       new OOMWarning("copying region", er);
@@ -1963,7 +2043,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
               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[]
@@ -1976,6 +2056,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void pasteNew_actionPerformed(ActionEvent e)
   {
@@ -1988,6 +2069,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void pasteThis_actionPerformed(ActionEvent e)
   {
@@ -2000,6 +2082,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param newAlignment
    *          true to paste to a new alignment, otherwise add to this.
    */
+
   void paste(boolean newAlignment)
   {
     boolean externalPaste = true;
@@ -2035,12 +2118,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       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++)
@@ -2065,10 +2150,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       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
         {
@@ -2084,8 +2169,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         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
@@ -2266,8 +2351,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           }
           buildSortByAnnotationScoresMenu();
         }
-        viewport.firePropertyChange("alignment", null,
-                alignment.getSequences());
+        viewport.notifyAlignment();
         if (alignPanels != null)
         {
           for (AlignmentPanel ap : alignPanels)
@@ -2287,10 +2371,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 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);
         }
 
@@ -2342,11 +2425,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       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);
       }
 
@@ -2385,6 +2467,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * Action Cut (delete and copy) the selected region
    */
+
   @Override
   protected void cut_actionPerformed()
   {
@@ -2395,6 +2478,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * Performs menu option to Delete the currently selected region
    */
+
   @Override
   protected void delete_actionPerformed()
   {
@@ -2405,56 +2489,63 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    Runnable okAction = new Runnable() 
-    {
-               @Override
-               public void run() 
-               {
-                   SequenceI[] cut = sg.getSequences()
-                           .toArray(new SequenceI[sg.getSize()]);
-
-                   addHistoryItem(new EditCommand(
-                           MessageManager.getString("label.cut_sequences"), Action.CUT,
-                           cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1,
-                           viewport.getAlignment()));
-
-                   viewport.setSelectionGroup(null);
-                   viewport.sendSelection();
-                   viewport.getAlignment().deleteGroup(sg);
-
-                   viewport.firePropertyChange("alignment", null,
-                           viewport.getAlignment().getSequences());
-                   if (viewport.getAlignment().getHeight() < 1)
-                   {
-                     try
-                     {
-                       AlignFrame.this.setClosed(true);
-                     } catch (Exception ex)
-                     {
-                     }
-                   }
-               }};
+    Runnable okAction = new Runnable()
+    {
+
+      @Override
+      public void run()
+      {
+        SequenceI[] cut = sg.getSequences()
+                .toArray(new SequenceI[sg.getSize()]);
+
+        addHistoryItem(new EditCommand(
+                MessageManager.getString("label.cut_sequences"), Action.CUT,
+                cut, sg.getStartRes(),
+                sg.getEndRes() - sg.getStartRes() + 1,
+                viewport.getAlignment()));
+
+        viewport.setSelectionGroup(null);
+        viewport.sendSelection();
+        viewport.getAlignment().deleteGroup(sg);
+
+        viewport.notifyAlignment();
+
+        if (viewport.getAlignment().getHeight() < 1)
+        {
+          try
+          {
+            AlignFrame.this.setClosed(true);
+          } catch (Exception ex)
+          {
+          }
+        }
+      }
+    };
 
     /*
      * If the cut affects all sequences, prompt for confirmation
      */
-    boolean wholeHeight = sg.getSize() == viewport.getAlignment().getHeight();
+    boolean wholeHeight = sg.getSize() == viewport.getAlignment()
+            .getHeight();
     boolean wholeWidth = (((sg.getEndRes() - sg.getStartRes())
             + 1) == viewport.getAlignment().getWidth()) ? true : false;
-       if (wholeHeight && wholeWidth)
-       {
-           JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop);
-               dialog.setResponseHandler(0, okAction); // 0 = OK_OPTION
-           Object[] options = new Object[] { MessageManager.getString("action.ok"),
-                   MessageManager.getString("action.cancel") };
-               dialog.showDialog(MessageManager.getString("warn.delete_all"),
-                   MessageManager.getString("label.delete_all"),
-                   JvOptionPane.DEFAULT_OPTION, JvOptionPane.PLAIN_MESSAGE, null,
-                   options, options[0]);
-       } else 
-       {
-               okAction.run();
-       }
+    if (wholeHeight && wholeWidth)
+    {
+      JvOptionPane dialog = JvOptionPane
+              .newOptionDialog(Desktop.getDesktopPane());
+      dialog.setResponseHandler(0, okAction); // 0 = OK_OPTION
+      Object[] options = new Object[] {
+          MessageManager.getString("action.ok"),
+          MessageManager.getString("action.cancel") };
+      dialog.showDialog(MessageManager.getString("warn.delete_all"),
+              MessageManager.getString("label.delete_all"),
+              JvOptionPane.DEFAULT_OPTION, JvOptionPane.PLAIN_MESSAGE, null,
+              options, options[0]);
+    }
+    else
+    {
+      okAction.run();
+    }
   }
 
   /**
@@ -2463,6 +2554,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void deleteGroups_actionPerformed(ActionEvent e)
   {
@@ -2480,21 +2572,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @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();
   }
 
   /**
@@ -2503,24 +2585,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @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.setSelectionGroup(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();
   }
 
   /**
@@ -2529,6 +2598,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void invertSequenceMenuItem_actionPerformed(ActionEvent e)
   {
@@ -2536,7 +2606,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     if (sg == null)
     {
-      selectAllSequenceMenuItem_actionPerformed(null);
+      alignPanel.selectAllSequences();
 
       return;
     }
@@ -2568,6 +2638,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void remove2LeftMenuItem_actionPerformed(ActionEvent e)
   {
@@ -2580,6 +2651,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void remove2RightMenuItem_actionPerformed(ActionEvent e)
   {
@@ -2626,8 +2698,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 column, viewport.getAlignment());
       }
 
-      setStatus(MessageManager
-              .formatMessage("label.removed_columns", new String[]
+      setStatus(MessageManager.formatMessage("label.removed_columns",
+              new String[]
               { Integer.valueOf(trimRegion.getSize()).toString() }));
 
       addHistoryItem(trimRegion);
@@ -2641,8 +2713,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         }
       }
 
-      viewport.firePropertyChange("alignment", null,
-              viewport.getAlignment().getSequences());
+      viewport.notifyAlignment();
+
     }
   }
 
@@ -2652,6 +2724,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void removeGappedColumnMenuItem_actionPerformed(ActionEvent e)
   {
@@ -2676,8 +2749,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     addHistoryItem(removeGapCols);
 
-    setStatus(MessageManager
-            .formatMessage("label.removed_empty_columns", new Object[]
+    setStatus(MessageManager.formatMessage("label.removed_empty_columns",
+            new Object[]
             { Integer.valueOf(removeGapCols.getSize()).toString() }));
 
     // This is to maintain viewport position on first residue
@@ -2691,8 +2764,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // if (viewport.hasHiddenColumns)
     // viewport.getColumnSelection().compensateForEdits(shifts);
     ranges.setStartRes(seq.findIndex(startRes) - 1);
-    viewport.firePropertyChange("alignment", null,
-            viewport.getAlignment().getSequences());
+    viewport.notifyAlignment();
+
 
   }
 
@@ -2702,6 +2775,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void removeAllGapsMenuItem_actionPerformed(ActionEvent e)
   {
@@ -2729,9 +2803,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             viewport.getAlignment()));
 
     viewport.getRanges().setStartRes(seq.findIndex(startRes) - 1);
-
-    viewport.firePropertyChange("alignment", null,
-            viewport.getAlignment().getSequences());
+    viewport.notifyAlignment();
 
   }
 
@@ -2741,12 +2813,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void padGapsMenuitem_actionPerformed(ActionEvent e)
   {
     viewport.setPadGaps(padGapsMenuitem.isSelected());
-    viewport.firePropertyChange("alignment", null,
-            viewport.getAlignment().getSequences());
+    viewport.notifyAlignment();
+
   }
 
   /**
@@ -2755,6 +2828,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void findMenuItem_actionPerformed(ActionEvent e)
   {
@@ -2764,6 +2838,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * Create a new view of the current alignment.
    */
+
   @Override
   public void newView_actionPerformed(ActionEvent e)
   {
@@ -2779,6 +2854,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    *          if true then duplicate all annnotation, groups and settings
    * @return new alignment panel, already displayed.
    */
+
   public AlignmentPanel newView(String viewTitle, boolean copyAnnotation)
   {
     /*
@@ -2799,8 +2875,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     if (viewport.getViewName() == null)
     {
-      viewport.setViewName(MessageManager
-              .getString("label.view_name_original"));
+      viewport.setViewName(
+              MessageManager.getString("label.view_name_original"));
     }
 
     /*
@@ -2851,6 +2927,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param viewTitle
    * @return
    */
+
   protected String getNewViewName(String viewTitle)
   {
     int index = Desktop.getViewCount(viewport.getSequenceSetId());
@@ -2885,6 +2962,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param comps
    * @return
    */
+
   protected List<String> getExistingViewNames(List<Component> comps)
   {
     List<String> existingNames = new ArrayList<>();
@@ -2905,6 +2983,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * Explode tabbed views into separate windows.
    */
+
   @Override
   public void expandViews_actionPerformed(ActionEvent e)
   {
@@ -2914,10 +2993,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * 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);
   }
 
   /**
@@ -2926,6 +3006,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void font_actionPerformed(ActionEvent e)
   {
@@ -2938,6 +3019,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void seqLimit_actionPerformed(ActionEvent e)
   {
@@ -2967,6 +3049,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @see jalview.jbgui.GAlignFrame#followHighlight_actionPerformed()
    */
+
   @Override
   protected void followHighlight_actionPerformed()
   {
@@ -2988,6 +3071,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void colourTextMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3001,6 +3085,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void wrapMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3037,6 +3122,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param toggleSeqs
    * @param toggleCols
    */
+
   protected void toggleHiddenRegions(boolean toggleSeqs, boolean toggleCols)
   {
 
@@ -3104,6 +3190,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * jalview.jbgui.GAlignFrame#hideAllButSelection_actionPerformed(java.awt.
    * event.ActionEvent)
    */
+
   @Override
   public void hideAllButSelection_actionPerformed(ActionEvent e)
   {
@@ -3118,6 +3205,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * jalview.jbgui.GAlignFrame#hideAllSelection_actionPerformed(java.awt.event
    * .ActionEvent)
    */
+
   @Override
   public void hideAllSelection_actionPerformed(ActionEvent e)
   {
@@ -3137,6 +3225,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * jalview.jbgui.GAlignFrame#showAllhidden_actionPerformed(java.awt.event.
    * ActionEvent)
    */
+
   @Override
   public void showAllhidden_actionPerformed(ActionEvent e)
   {
@@ -3168,6 +3257,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void scaleAbove_actionPerformed(ActionEvent e)
   {
@@ -3182,6 +3272,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void scaleLeft_actionPerformed(ActionEvent e)
   {
@@ -3196,6 +3287,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void scaleRight_actionPerformed(ActionEvent e)
   {
@@ -3210,6 +3302,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void viewBoxesMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3223,6 +3316,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void viewTextMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3236,6 +3330,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void renderGapsMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3281,6 +3376,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param evt
    *          DOCUMENT ME!
    */
+
   @Override
   public void showSeqFeatures_actionPerformed(ActionEvent evt)
   {
@@ -3297,16 +3393,32 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @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
@@ -3326,7 +3438,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       JLabel textLabel = new JLabel();
       textLabel.setText(content);
       textLabel.setBackground(Color.WHITE);
-      
+
       pane = new JPanel(new BorderLayout());
       ((JPanel) pane).setOpaque(true);
       pane.setBackground(Color.WHITE);
@@ -3360,6 +3472,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void overviewMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3369,12 +3482,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
 
     JInternalFrame frame = new JInternalFrame();
-    
-    
 
     // BH 2019.07.26 we allow for an embedded
     // undecorated overview with defined size
-    frame.setName(Jalview.getAppID("overview"));
+    frame.setName(Platform.getAppID("overview"));
     //
     Dimension dim = Platform.getDimIfEmbedded(frame, -1, -1);
     if (dim != null && dim.width == 0)
@@ -3399,13 +3510,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             "resize") != "none");
     Desktop.addInternalFrame(frame, MessageManager
             .formatMessage("label.overview_params", new Object[]
-            { this.getTitle() }), true, dim.width, dim.height, resizable,
-            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)
@@ -3434,6 +3546,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * CovariationColourScheme(viewport.getAlignment().getAlignmentAnnotation
    * ()[0])); }
    */
+
   @Override
   public void annotationColour_actionPerformed()
   {
@@ -3453,6 +3566,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param selected
    */
+
   @Override
   public void applyToAllGroups_actionPerformed(boolean selected)
   {
@@ -3465,6 +3579,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param name
    *          the name (not the menu item label!) of the colour scheme
    */
+
   @Override
   public void changeColour_actionPerformed(String name)
   {
@@ -3482,8 +3597,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
      * otherwise set the chosen colour scheme (or null for 'None')
      */
     ColourSchemeI cs = ColourSchemes.getInstance().getColourScheme(name,
-            viewport,
-            viewport.getAlignment(), viewport.getHiddenRepSequences());
+            viewport, viewport.getAlignment(),
+            viewport.getHiddenRepSequences());
     changeColour(cs);
   }
 
@@ -3492,6 +3607,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param cs
    */
+
   @Override
   public void changeColour(ColourSchemeI cs)
   {
@@ -3506,6 +3622,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * Show the PID threshold slider panel
    */
+
   @Override
   protected void modifyPID_actionPerformed()
   {
@@ -3517,6 +3634,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * Show the Conservation slider panel
    */
+
   @Override
   protected void modifyConservation_actionPerformed()
   {
@@ -3528,6 +3646,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * Action on selecting or deselecting (Colour) By Conservation
    */
+
   @Override
   public void conservationMenuItem_actionPerformed(boolean selected)
   {
@@ -3549,6 +3668,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * Action on selecting or deselecting (Colour) Above PID Threshold
    */
+
   @Override
   public void abovePIDThreshold_actionPerformed(boolean selected)
   {
@@ -3577,6 +3697,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void sortPairwiseMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3594,6 +3715,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void sortIDMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3610,6 +3732,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void sortLengthMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3626,6 +3749,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void sortGroupMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3643,6 +3767,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void removeRedundancyMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3655,6 +3780,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   public void pairwiseAlignmentMenuItem_actionPerformed(ActionEvent e)
   {
@@ -3680,11 +3806,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @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();
     }
   }
 
@@ -3710,6 +3839,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param options
    *          parameters for the distance or similarity calculation
    */
+
   void newTreePanel(String type, String modelName,
           SimilarityParamsI options)
   {
@@ -3727,7 +3857,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
         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(
@@ -3771,6 +3901,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param order
    *          DOCUMENT ME!
    */
+
   public void addSortByOrderMenuItem(String title,
           final AlignmentOrder order)
   {
@@ -3780,6 +3911,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     sort.add(item);
     item.addActionListener(new java.awt.event.ActionListener()
     {
+
       @Override
       public void actionPerformed(ActionEvent e)
       {
@@ -3806,6 +3938,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    *          the label used to retrieve scores for each sequence on the
    *          alignment
    */
+
   public void addSortByAnnotScoreMenuItem(JMenu sort,
           final String scoreLabel)
   {
@@ -3813,6 +3946,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     sort.add(item);
     item.addActionListener(new java.awt.event.ActionListener()
     {
+
       @Override
       public void actionPerformed(ActionEvent e)
       {
@@ -3838,6 +3972,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * rebuilding in subsequence calls.
    * 
    */
+
   @Override
   public void buildSortByAnnotationScoresMenu()
   {
@@ -3867,8 +4002,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       Enumeration<String> labels = scoreSorts.keys();
       while (labels.hasMoreElements())
       {
-        addSortByAnnotScoreMenuItem(sortByAnnotScore,
-                labels.nextElement());
+        addSortByAnnotScoreMenuItem(sortByAnnotScore, labels.nextElement());
       }
       sortByAnnotScore.setVisible(scoreSorts.size() > 0);
       scoreSorts.clear();
@@ -3879,41 +4013,38 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   /**
+   * 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)
         {
@@ -3927,6 +4058,21 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
   }
 
+  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();
@@ -3945,6 +4091,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * be submitted for multiple alignment.
    * 
    */
+
   public jalview.datamodel.AlignmentView gatherSequencesForAlignment()
   {
     // Now, check we have enough sequences
@@ -3990,6 +4137,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 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;
@@ -4023,6 +4171,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param e
    *          DOCUMENT ME!
    */
+
   @Override
   protected void loadTreeMenuItem_actionPerformed(ActionEvent e)
   {
@@ -4035,8 +4184,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     chooser.setToolTipText(
             MessageManager.getString("label.load_tree_file"));
 
-    chooser.setResponseHandler(0,new Runnable()
+    chooser.setResponseHandler(0, new Runnable()
     {
+
       @Override
       public void run()
       {
@@ -4050,7 +4200,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           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);
@@ -4058,7 +4209,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         }
         if (fin != null && fin.hasWarningMessage())
         {
-          JvOptionPane.showMessageDialog(Desktop.desktop,
+          JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                   fin.getWarningMessage(),
                   MessageManager.getString(
                           "label.possible_problem_with_tree_file"),
@@ -4100,6 +4251,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    *          position
    * @return TreePanel handle
    */
+
   public TreePanel showNewickTree(NewickFile nf, String treeTitle,
           AlignmentView input, int w, int h, int x, int y)
   {
@@ -4145,6 +4297,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * Generates menu items and listener event actions for web service clients
    * 
    */
+
   public void BuildWebServiceMenu()
   {
     while (buildingMenu)
@@ -4161,6 +4314,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     buildingMenu = true;
     new Thread(new Runnable()
     {
+
       @Override
       public void run()
       {
@@ -4195,9 +4349,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           final JMenu dismenu = new JMenu("Protein Disorder");
           // JAL-940 - only show secondary structure prediction services from
           // the legacy server
+          Hashtable<String, Vector<ServiceHandle>> ds = Discoverer
+                  .getInstance().getServices();
           if (// Cache.getDefault("SHOW_JWS1_SERVICES", true)
               // &&
-          Discoverer.services != null && (Discoverer.services.size() > 0))
+          ds != null && (ds.size() > 0))
           {
             // TODO: refactor to allow list of AbstractName/Handler bindings to
             // be
@@ -4205,15 +4361,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             // No MSAWS used any more:
             // Vector msaws = null; // (Vector)
             // Discoverer.services.get("MsaWS");
-            Vector<ServiceHandle> secstrpr = Discoverer.services
-                    .get("SecStrPred");
+            Vector<ServiceHandle> secstrpr = ds.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);
+                final ext.vamsas.ServiceHandle sh = secstrpr.get(i);
                 jalview.ws.WSMenuEntryProviderI impl = jalview.ws.jws1.Discoverer
                         .getServiceClient(sh);
                 int p = secstrmenu.getItemCount();
@@ -4238,6 +4392,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
           javax.swing.SwingUtilities.invokeLater(new Runnable()
           {
+
             @Override
             public void run()
             {
@@ -4257,10 +4412,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                   webService.add(me.webServiceNoServices);
                 }
                 // TODO: move into separate menu builder class.
-                boolean new_sspred = false;
+                // boolean new_sspred = false;
                 if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
                 {
-                  Jws2Discoverer jws2servs = Jws2Discoverer.getDiscoverer();
+                  Jws2Discoverer jws2servs = Jws2Discoverer.getInstance();
                   if (jws2servs != null)
                   {
                     if (jws2servs.hasServices())
@@ -4322,6 +4477,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param webService
    */
+
   protected void build_urlServiceMenu(JMenu webService)
   {
     // TODO: remove this code when 2.7 is released
@@ -4330,7 +4486,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
      * 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); }
@@ -4359,6 +4515,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @return true if Show Cross-references menu should be enabled
    */
+
   public boolean canShowProducts()
   {
     SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
@@ -4386,6 +4543,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         JMenuItem xtype = new JMenuItem(source);
         xtype.addActionListener(new ActionListener()
         {
+
           @Override
           public void actionPerformed(ActionEvent e)
           {
@@ -4418,6 +4576,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param source
    *          the database to show cross-references for
    */
+
   protected void showProductsFor(final SequenceI[] sel, final boolean _odna,
           final String source)
   {
@@ -4429,6 +4588,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 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)
   {
@@ -4447,8 +4607,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       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)
@@ -4457,8 +4617,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
               "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
     {
@@ -4486,6 +4646,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param format
    */
+
   public void setFileFormat(FileFormatI format)
   {
     this.currentFileFormat = format;
@@ -4500,11 +4661,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    *          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));
 
   }
 
@@ -4550,184 +4712,190 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     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 file = files.get(i);
+        String fileName = file.toString();
+        String pdbfn = "";
+        DataSourceType protocol = (file instanceof File
+                ? DataSourceType.FILE
+                : FormatAdapter.checkProtocol(fileName));
+        if (protocol == DataSourceType.FILE)
         {
-          try
+          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)
           {
-            // 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(file, 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[] { file, protocol, mtch });
+              continue;
+            }
+          }
+          // File wasn't named like one of the sequences or wasn't a PDB
+          // file.
+          filesnotmatched.add(file);
+        }
+      }
+      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();
     }
   }
 
@@ -4743,6 +4911,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param file
    *          either a filename or a URL string.
    */
+
   public void loadJalviewDataFile(Object file, DataSourceType sourceType,
           FileFormatI format, SequenceI assocSeq)
   {
@@ -4784,7 +4953,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             {
               // 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")
@@ -4881,7 +5050,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                       + (format != null
                               ? "(parsing as '" + format + "' file)"
                               : ""),
-              oom, Desktop.desktop);
+              oom, Desktop.getDesktopPane());
     }
   }
 
@@ -4907,6 +5076,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param state
    *          visible or invisible
    */
+
   public void setFeatureGroupState(String[] groups, boolean state)
   {
     jalview.api.FeatureRenderer fr = null;
@@ -4924,11 +5094,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     }
   }
 
-
   /**
    * 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)
   {
@@ -4999,6 +5169,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * On right mouse click on view tab, prompt for and set new view name.
    */
+
   @Override
   public void tabbedPane_mousePressed(MouseEvent e)
   {
@@ -5025,6 +5196,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * Open the dialog for regex description parsing.
    */
+
   @Override
   protected void extractScores_actionPerformed(ActionEvent e)
   {
@@ -5048,6 +5220,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * jalview.jbgui.GAlignFrame#showDbRefs_actionPerformed(java.awt.event.ActionEvent
    * )
    */
+
   @Override
   protected void showDbRefs_actionPerformed(ActionEvent e)
   {
@@ -5060,6 +5233,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @seejalview.jbgui.GAlignFrame#showNpFeats_actionPerformed(java.awt.event.
    * ActionEvent)
    */
+
   @Override
   protected void showNpFeats_actionPerformed(ActionEvent e)
   {
@@ -5072,6 +5246,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param av
    */
+
   public boolean closeView(AlignViewportI av)
   {
     if (viewport == av)
@@ -5115,6 +5290,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             Cache.getDefault(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES, true));
     trimrs.addActionListener(new ActionListener()
     {
+
       @Override
       public void actionPerformed(ActionEvent e)
       {
@@ -5136,6 +5312,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
         new Thread(new Runnable()
         {
+
           @Override
           public void run()
           {
@@ -5147,6 +5324,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                     alignPanel.alignFrame.featureSettings, isNucleotide);
             dbRefFetcher.addListener(new FetchFinishedListenerI()
             {
+
               @Override
               public void finished()
               {
@@ -5170,16 +5348,21 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     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();
@@ -5203,9 +5386,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
               }
               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()
                 {
@@ -5230,6 +5412,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                         dbRefFetcher
                                 .addListener(new FetchFinishedListenerI()
                                 {
+
                                   @Override
                                   public void finished()
                                   {
@@ -5264,6 +5447,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                         { src.getDbSource() }));
                 fetchr.addActionListener(new ActionListener()
                 {
+
                   @Override
                   public void actionPerformed(ActionEvent e)
                   {
@@ -5284,6 +5468,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                         dbRefFetcher
                                 .addListener(new FetchFinishedListenerI()
                                 {
+
                                   @Override
                                   public void finished()
                                   {
@@ -5352,6 +5537,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                           dbRefFetcher
                                   .addListener(new FetchFinishedListenerI()
                                   {
+
                                     @Override
                                     public void finished()
                                     {
@@ -5403,23 +5589,23 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * 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
@@ -5436,6 +5622,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * jalview.jbgui.GAlignFrame#showUnconservedMenuItem_actionPerformed(java.
    * awt.event.ActionEvent)
    */
+
   @Override
   protected void showUnconservedMenuItem_actionPerformed(ActionEvent e)
   {
@@ -5450,6 +5637,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * jalview.jbgui.GAlignFrame#showGroupConsensus_actionPerformed(java.awt.event
    * .ActionEvent)
    */
+
   @Override
   protected void showGroupConsensus_actionPerformed(ActionEvent e)
   {
@@ -5465,6 +5653,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * jalview.jbgui.GAlignFrame#showGroupConservation_actionPerformed(java.awt
    * .event.ActionEvent)
    */
+
   @Override
   protected void showGroupConservation_actionPerformed(ActionEvent e)
   {
@@ -5479,6 +5668,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * jalview.jbgui.GAlignFrame#showConsensusHistogram_actionPerformed(java.awt
    * .event.ActionEvent)
    */
+
   @Override
   protected void showConsensusHistogram_actionPerformed(ActionEvent e)
   {
@@ -5493,6 +5683,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * jalview.jbgui.GAlignFrame#showConsensusProfile_actionPerformed(java.awt
    * .event.ActionEvent)
    */
+
   @Override
   protected void showSequenceLogo_actionPerformed(ActionEvent e)
   {
@@ -5522,6 +5713,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * jalview.jbgui.GAlignFrame#makeGrpsFromSelection_actionPerformed(java.awt
    * .event.ActionEvent)
    */
+
   @Override
   protected void makeGrpsFromSelection_actionPerformed(ActionEvent e)
   {
@@ -5573,6 +5765,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param alignmentPanel
    */
+
   public void setDisplayedView(AlignmentPanel alignmentPanel)
   {
     if (!viewport.getSequenceSetId()
@@ -5597,6 +5790,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param forAlignment
    *          update non-sequence-related annotations
    */
+
   @Override
   protected void setAnnotationsVisibility(boolean visible,
           boolean forSequences, boolean forAlignment)
@@ -5630,6 +5824,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   /**
    * Store selected annotation sort order for the view and repaint.
    */
+
   @Override
   protected void sortAnnotations_actionPerformed()
   {
@@ -5643,6 +5838,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @return alignment panels in this alignment frame
    */
+
   public List<? extends AlignmentViewPanel> getAlignPanels()
   {
     // alignPanels is never null
@@ -5654,6 +5850,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 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
@@ -5704,6 +5901,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @param show
    */
+
   @Override
   protected void showComplement_actionPerformed(boolean show)
   {
@@ -5718,6 +5916,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * Generate the reverse (optionally complemented) of the selected sequences,
    * and add them to the alignment
    */
+
   @Override
   protected void showReverse_actionPerformed(boolean complement)
   {
@@ -5743,6 +5942,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * AlignFrame is set as currentAlignFrame in Desktop, to allow the script to
    * be targeted at this alignment.
    */
+
   @Override
   protected void runGroovy_actionPerformed()
   {
@@ -5756,7 +5956,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       } 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);
@@ -5776,6 +5976,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * @param columnsContaining
    * @return
    */
+
   public boolean hideFeatureColumns(String featureType,
           boolean columnsContaining)
   {
@@ -5808,6 +6009,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 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();
@@ -5835,6 +6037,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 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)
@@ -5854,6 +6057,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     final AlignFrame us = this;
     chooser.setResponseHandler(0, new Runnable()
     {
+
       @Override
       public void run()
       {
@@ -5868,6 +6072,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   private Rectangle lastFeatureSettingsBounds = null;
+
   @Override
   public void setFeatureSettingsGeometry(Rectangle bounds)
   {
@@ -5904,12 +6109,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    *          true is visible
    * @return list
    */
+
   public String[] getFeatureGroupsOfState(boolean visible)
   {
     jalview.api.FeatureRenderer fr = null;
     if (alignPanel != null
-            && (fr = alignPanel
-                    .getFeatureRenderer()) != null)
+            && (fr = alignPanel.getFeatureRenderer()) != null)
     {
       List<String> gps = fr.getGroups(visible);
       String[] _gps = gps.toArray(new String[gps.size()]);
@@ -5922,6 +6127,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    * 
    * @return list of feature groups on the view
    */
+
   public String[] getFeatureGroups()
   {
     jalview.api.FeatureRenderer fr = null;
@@ -5941,43 +6147,47 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     alignPanel.getSeqPanel().selection(sel, csel, hidden, null);
   }
 
-
-}
-
-class PrintThread extends Thread
-{
-  AlignmentPanel ap;
-
-  public PrintThread(AlignmentPanel ap)
+  public int getID()
   {
-    this.ap = ap;
+    return id;
   }
 
-  static PageFormat pf;
-
-  @Override
-  public void run()
+  static class PrintThread extends Thread
   {
-    PrinterJob printJob = PrinterJob.getPrinterJob();
+    AlignmentPanel ap;
 
-    if (pf != null)
+    public PrintThread(AlignmentPanel ap)
     {
-      printJob.setPrintable(ap, pf);
-    }
-    else
-    {
-      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();
+        }
       }
     }
   }
 }
+
index 1849447..7ab84ad 100644 (file)
@@ -223,7 +223,7 @@ f   */
 
     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));
@@ -394,7 +394,7 @@ f   */
     if (align != null)
     {
       StructureSelectionManager ssm = StructureSelectionManager
-              .getStructureSelectionManager(Desktop.instance);
+              .getStructureSelectionManager(Desktop.getInstance());
       ssm.registerMappings(align.getCodonFrames());
     }
 
@@ -416,7 +416,7 @@ f   */
       if (mappings != null)
       {
         StructureSelectionManager ssm = StructureSelectionManager
-                .getStructureSelectionManager(Desktop.instance);
+                .getStructureSelectionManager(Desktop.getInstance());
         for (AlignedCodonFrame acf : mappings)
         {
           if (noReferencesTo(acf))
@@ -542,7 +542,7 @@ f   */
   public void sendSelection()
   {
     jalview.structure.StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance)
+            .getStructureSelectionManager(Desktop.getInstance())
             .sendSelection(new SequenceGroup(getSelectionGroup()),
                     new ColumnSelection(getColumnSelection()),
                     new HiddenColumns(getAlignment().getHiddenColumns()),
@@ -588,7 +588,7 @@ f   */
   public StructureSelectionManager getStructureSelectionManager()
   {
     return StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance);
+            .getStructureSelectionManager(Desktop.getInstance());
   }
 
   @Override
@@ -754,7 +754,8 @@ f   */
     }
 
     ranges.setEndSeq(getAlignment().getHeight() - 1); // BH 2019.04.18
-    firePropertyChange("alignment", null, getAlignment().getSequences());
+    notifyAlignment();
+
   }
 
   /**
@@ -780,7 +781,7 @@ f   */
      * dialog responses 0, 1, 2 (even though JOptionPane shows them
      * in reverse order)
      */
-    JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop)
+    JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.getDesktopPane())
             .setResponseHandler(NO_SPLIT, new Runnable()
             {
               @Override
@@ -1147,4 +1148,5 @@ f   */
   {
     this.viewName = viewName;
   }
+
 }
index eb612c8..b57bc08 100644 (file)
  */
 package jalview.gui;
 
-import jalview.analysis.AnnotationSorter;
-import jalview.api.AlignViewportI;
-import jalview.api.AlignmentViewPanel;
-import jalview.bin.Cache;
-import jalview.bin.Jalview;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.SearchResultsI;
-import jalview.datamodel.SequenceFeature;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.gui.ImageExporter.ImageWriterI;
-import jalview.io.HTMLOutput;
-import jalview.jbgui.GAlignmentPanel;
-import jalview.math.AlignmentDimension;
-import jalview.schemes.ResidueProperties;
-import jalview.structure.StructureSelectionManager;
-import jalview.util.Comparison;
-import jalview.util.ImageMaker;
-import jalview.util.MessageManager;
-import jalview.viewmodel.ViewportListenerI;
-import jalview.viewmodel.ViewportRanges;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Container;
@@ -67,6 +44,30 @@ import java.util.List;
 
 import javax.swing.SwingUtilities;
 
+import jalview.analysis.AnnotationSorter;
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SearchResultsI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.gui.ImageExporter.ImageWriterI;
+import jalview.io.HTMLOutput;
+import jalview.jbgui.GAlignmentPanel;
+import jalview.math.AlignmentDimension;
+import jalview.schemes.ResidueProperties;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.Comparison;
+import jalview.util.ImageMaker;
+import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
+
 /**
  * DOCUMENT ME!
  * 
@@ -122,7 +123,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   public AlignmentPanel(AlignFrame af, final AlignViewport av)
   {
-//     setBackground(Color.white);  // BH 2019
+    // setBackground(Color.white); // BH 2019
     alignFrame = af;
     this.av = av;
     setSeqPanel(new SeqPanel(av, this));
@@ -173,6 +174,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
           ranges.setViewportWidth(widthInRes);
           ranges.setViewportHeight(heightInSeq);
         }
+        repaint();
       }
 
     });
@@ -183,10 +185,17 @@ public class AlignmentPanel extends GAlignmentPanel implements
       @Override
       public void propertyChange(PropertyChangeEvent evt)
       {
-        if (evt.getPropertyName().equals("alignment"))
-        {
+        switch (evt.getPropertyName()) {  
+        case AlignmentViewport.PROPERTY_SEQUENCE:
+          updateScrollBarsFromRanges();
+          if (annotationPanel != null)
+            annotationPanel.paintImmediately(0,  0, getWidth(), getHeight());
+          break;
+        case AlignmentViewport.PROPERTY_ALIGNMENT:
+          updateScrollBarsFromRanges();
           PaintRefresher.Refresh(ap, av.getSequenceSetId(), true, true);
           alignmentChanged();
+          break;
         }
       }
     };
@@ -259,32 +268,37 @@ public class AlignmentPanel extends GAlignmentPanel implements
     int oldWidth = av.getIdWidth();
 
     // calculate sensible default width when no preference is available
-    Dimension r = null;
+    Dimension d = null;
     if (av.getIdWidth() < 0)
     {
-      int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
-      int idWidth = Math.min(afwidth - 200, 2 * afwidth / 3);
-      int maxwidth = Math.max(IdwidthAdjuster.MIN_ID_WIDTH, idWidth);
-      r = calculateIdWidth(maxwidth);
-      av.setIdWidth(r.width);
+      int maxWidth = getMaxWidth();
+      d = calculateIdWidth(maxWidth);
+      av.setIdWidth(d.width);
     }
     else
     {
-      r = new Dimension();
-      r.width = av.getIdWidth();
-      r.height = 0;
+      d = new Dimension();
+      d.width = av.getIdWidth();
+      d.height = 0;
     }
 
     /*
      * fudge: if desired width has changed, update layout
      * (see also paintComponent - updates layout on a repaint)
      */
-    if (r.width != oldWidth)
+    if (d.width != oldWidth)
     {
-      idPanelHolder.setPreferredSize(r);
+      idPanelHolder.setPreferredSize(d);
       validate();
     }
-    return r;
+    return d;
+  }
+
+  public int getMaxWidth()
+  {
+    int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
+    int idWidth = Math.min(afwidth - 200, 2 * afwidth / 3);
+    return Math.max(IdwidthAdjuster.MIN_ID_WIDTH, idWidth);
   }
 
   /**
@@ -296,10 +310,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * @return Dimension giving the maximum width of the alignment label panel
    *         that should be used.
    */
-  protected Dimension calculateIdWidth(int maxwidth)
+  public Dimension calculateIdWidth(int maxwidth)
   {
-    Container c = new Container();
-
+    Container c = this;// new Container();
     FontMetrics fm = c.getFontMetrics(
             new Font(av.font.getName(), Font.ITALIC, av.font.getSize()));
 
@@ -307,10 +320,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
     int i = 0;
     int idWidth = 0;
 
+    boolean withSuffix = av.getShowJVSuffix();
+
     while ((i < al.getHeight()) && (al.getSequenceAt(i) != null))
     {
       SequenceI s = al.getSequenceAt(i);
-      String id = s.getDisplayId(av.getShowJVSuffix());
+      String id = s.getDisplayId(withSuffix);
       int stringWidth = fm.stringWidth(id);
       idWidth = Math.max(idWidth, stringWidth);
       i++;
@@ -471,8 +486,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
         /*
          * Scroll down to make end of search results visible
          */
-        setScrollValues(ranges.getStartRes(), starts + seqIndex - ends
-                + 1);
+        setScrollValues(ranges.getStartRes(), starts + seqIndex - ends + 1);
       }
       /*
        * Else results are already visible - no need to scroll
@@ -554,17 +568,24 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     // BH 2018.04.18 comment: addNotify() is not appropriate here. We
     // are not changing ancestors, and keyboard action listeners do
-    // not need to be reset. addNotify() is a very expensive operation,
+    // not need to be reset, and most importantly, we can't be sure we are actually
+    // connected to resources. 
+    
+    // addNotify() is a very expensive operation,
     // requiring a full re-layout of all parents and children.
+    
     // Note in JComponent:
+    
     // This method is called by the toolkit internally and should
     // not be called directly by programs.
+    
     // I note that addNotify() is called in several areas of Jalview.
 
     int annotationHeight = getAnnotationPanel().adjustPanelHeight();
     annotationHeight = getAnnotationPanel()
             .adjustForAlignFrame(adjustPanelHeight, annotationHeight);
 
+    // BH no!!
     hscroll.addNotify();
     annotationScroller.setPreferredSize(
             new Dimension(annotationScroller.getWidth(), annotationHeight));
@@ -572,7 +593,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
     Dimension e = idPanel.getSize();
     alabels.setSize(new Dimension(e.width, annotationHeight));
 
-
     annotationSpaceFillerHolder.setPreferredSize(new Dimension(
             annotationSpaceFillerHolder.getWidth(), annotationHeight));
     annotationScroller.validate();
@@ -587,10 +607,17 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   public void updateLayout()
   {
+    // BH 2020.06.09 avoiding negative values for SequencePanel and SeqCanvas
+    // dimensions.
+    
+     if (getTopLevelAncestor() == null)
+     return;
+
+    
+    ViewportRanges ranges = av.getRanges();
     fontChanged();
     setAnnotationVisible(av.isShowAnnotation());
     boolean wrap = av.getWrapAlignment();
-    ViewportRanges ranges = av.getRanges();
     ranges.setStartSeq(0);
     scalePanelHolder.setVisible(!wrap);
     hscroll.setVisible(!wrap);
@@ -630,7 +657,14 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     idSpaceFillerPanel1.setVisible(!wrap);
 
-    repaint();
+    // System.out.println("ap dim = " + getSize());
+
+    // these values will go negative if getSize() returns (0,0):
+
+    // System.out.println("seqpan dim = " + getSeqPanel().getSize());
+    // System.out.println("seqcan dim = " + getSeqPanel().seqCanvas.getSize());
+
+    // BH not added to anything yet! repaint();
   }
 
   /**
@@ -642,10 +676,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
    *          visible row to scroll to
    * 
    */
-  public void setScrollValues(int xpos, int ypos)
+  public void setScrollValues(int x, int y)
   {
-    int x = xpos;
-    int y = ypos;
 
     if (av == null || av.getAlignment() == null)
     {
@@ -660,46 +692,24 @@ public class AlignmentPanel extends GAlignmentPanel implements
     {
       int width = av.getAlignment().getVisibleWidth();
       int height = av.getAlignment().getHeight();
-
-      hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
-      vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
-
-      if (hextent > width)
-      {
-        hextent = width;
-      }
-
-      if (vextent > height)
-      {
-        vextent = height;
-      }
-
-      if ((hextent + x) > width)
-      {
-        x = width - hextent;
-      }
-
-      if ((vextent + y) > height)
-      {
-        y = height - vextent;
-      }
-
-      if (y < 0)
-      {
-        y = 0;
-      }
-
-      if (x < 0)
-      {
-        x = 0;
-      }
-
-      // update the scroll values
-      hscroll.setValues(x, hextent, 0, width);
-      vscroll.setValues(y, vextent, 0, height);
+      
+      hextent = Math.min(getSeqPanel().seqCanvas.getWidth() / av.getCharWidth(),  width);
+      vextent = Math.min(getSeqPanel().seqCanvas.getHeight() / av.getCharHeight(),  height);
+  
+      x = Math.max(0, Math.min(x,  width - hextent));
+      y = Math.max(0, Math.min(y,  height - vextent));
+      
+      updateRanges(x, y);
+      updateScrollBars(x, y, width, height);
     }
   }
 
+  private void updateScrollBars(int x, int y, int width, int height) 
+  {
+    hscroll.setValues(x, hextent, 0, width);
+    vscroll.setValues(y, vextent, 0, height);
+  }
+
   /**
    * Respond to adjustment event when horizontal or vertical scrollbar is
    * changed
@@ -716,41 +726,54 @@ public class AlignmentPanel extends GAlignmentPanel implements
       return;
     }
 
-    ViewportRanges ranges = av.getRanges();
-
     if (evt.getSource() == hscroll)
     {
+      if (!updateRanges(hscroll.getValue(), Integer.MIN_VALUE))
+        return;
+    }
+    else if (evt.getSource() == vscroll)
+    {
+      if (!updateRanges(Integer.MIN_VALUE, vscroll.getValue()))
+        return;
+    }
+    repaint();
+  }
+
+  private boolean updateRanges(int x, int y)
+  {
+    ViewportRanges ranges = av.getRanges();
+    boolean isChanged = false;
+    if (x != Integer.MIN_VALUE)
+    {
       int oldX = ranges.getStartRes();
       int oldwidth = ranges.getViewportWidth();
-      int x = hscroll.getValue();
       int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
 
       // if we're scrolling to the position we're already at, stop
       // this prevents infinite recursion of events when the scroll/viewport
       // ranges values are the same
-      if ((x == oldX) && (width == oldwidth))
+      if (width > 0 && (x != oldX || width != oldwidth))
       {
-        return;
+        ranges.setViewportStartAndWidth(x, width);
+        isChanged = true;
       }
-      ranges.setViewportStartAndWidth(x, width);
     }
-    else if (evt.getSource() == vscroll)
+    if (y != Integer.MIN_VALUE)
     {
       int oldY = ranges.getStartSeq();
       int oldheight = ranges.getViewportHeight();
-      int y = vscroll.getValue();
       int height = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
 
       // if we're scrolling to the position we're already at, stop
       // this prevents infinite recursion of events when the scroll/viewport
       // ranges values are the same
-      if ((y == oldY) && (height == oldheight))
+      if (height > 0 && (y != oldY || height != oldheight))
       {
-        return;
+        ranges.setViewportStartAndHeight(y, height);
+        isChanged = true;
       }
-      ranges.setViewportStartAndHeight(y, height);
     }
-    repaint();
+    return isChanged;
   }
 
   /**
@@ -829,7 +852,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
             av.isShowAutocalculatedAbove());
     sorter.sort(getAlignment().getAlignmentAnnotation(),
             av.getSortAnnotationsBy());
-    repaint();
 
     if (updateStructures)
     {
@@ -837,11 +859,14 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
     if (updateOverview)
     {
+      alignFrame.repaint();
 
       if (overviewPanel != null)
       {
         overviewPanel.updateOverviewImage();
       }
+    } else {
+      repaint();
     }
   }
 
@@ -861,7 +886,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
      * though I still think this call should be elsewhere.
      */
     ViewportRanges ranges = av.getRanges();
-    setScrollValues(ranges.getStartRes(), ranges.getStartSeq());
+    // setScrollValues(ranges.getStartRes(), ranges.getStartSeq());
     super.paintComponent(g);
   }
 
@@ -1020,8 +1045,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
      * single graphics context), then reset to (0, scale height)
      */
     alignmentGraphics.translate(alignmentGraphicsOffset, scaleHeight);
-    getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics, startRes,
-            endRes, startSeq, endSeq - 1);
+    getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics,
+            startRes, endRes, startSeq, endSeq - 1);
     alignmentGraphics.translate(-alignmentGraphicsOffset, 0);
 
     if (av.isShowAnnotation() && (endSeq == alignmentHeight))
@@ -1065,11 +1090,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * 
    * @throws PrinterException
    */
-  public int printWrappedAlignment(int pageWidth, int pageHeight, int pageNumber,
-          Graphics g) throws PrinterException
+  public int printWrappedAlignment(int pageWidth, int pageHeight,
+          int pageNumber, Graphics g) throws PrinterException
   {
-    getSeqPanel().seqCanvas.calculateWrappedGeometry(getWidth(),
-            getHeight());
+    getSeqPanel().seqCanvas.calculateWrappedGeometry();
     int annotationHeight = 0;
     if (av.isShowAnnotation())
     {
@@ -1118,8 +1142,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     g.translate(idWidth, 0);
 
-    getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(g, pageWidth - idWidth,
-            totalHeight, 0);
+    getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(g,
+            pageWidth - idWidth, totalHeight, 0);
 
     if ((pageNumber * pageHeight) < totalHeight)
     {
@@ -1284,13 +1308,13 @@ public class AlignmentPanel extends GAlignmentPanel implements
             String triplet = null;
             if (av.getAlignment().isNucleotide())
             {
-              triplet = ResidueProperties.nucleotideName.get(seq
-                      .getCharAt(column) + "");
+              triplet = ResidueProperties.nucleotideName
+                      .get(seq.getCharAt(column) + "");
             }
             else
             {
-              triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(column)
-                      + "");
+              triplet = ResidueProperties.aa2Triplet
+                      .get(seq.getCharAt(column) + "");
             }
 
             if (triplet == null)
@@ -1307,7 +1331,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
                 text.append("<area shape=\"rect\" coords=\"")
                         .append((idWidth + column * av.getCharWidth()))
                         .append(",").append(sy).append(",")
-                        .append((idWidth + (column + 1) * av.getCharWidth()))
+                        .append((idWidth
+                                + (column + 1) * av.getCharWidth()))
                         .append(",").append((av.getCharHeight() + sy))
                         .append("\"").append(" onMouseOver=\"toolTip('")
                         .append(seqPos).append(" ").append(triplet);
@@ -1333,7 +1358,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
             }
             if (!Comparison.isGap(seq.getCharAt(column)))
             {
-              List<SequenceFeature> features = seq.findFeatures(column, column);
+              List<SequenceFeature> features = seq.findFeatures(column,
+                      column);
               for (SequenceFeature sf : features)
               {
                 if (sf.isContactFeature())
@@ -1702,10 +1728,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
   public void propertyChange(PropertyChangeEvent evt)
   {
     // update this panel's scroll values based on the new viewport ranges values
-    ViewportRanges ranges = av.getRanges();
-    int x = ranges.getStartRes();
-    int y = ranges.getStartSeq();
-    setScrollValues(x, y);
+    updateScrollBarsFromRanges();
 
     // now update any complementary alignment (its viewport ranges object
     // is different so does not get automatically updated)
@@ -1717,6 +1740,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
   }
 
+  void updateScrollBarsFromRanges()
+  {
+    ViewportRanges ranges = av.getRanges();
+    setScrollValues(ranges.getStartRes(), ranges.getStartSeq());
+  }
+
   /**
    * Set the reference to the PCA/Tree chooser dialog for this panel. This
    * reference should be nulled when the dialog is closed.
@@ -1854,4 +1883,84 @@ public class AlignmentPanel extends GAlignmentPanel implements
     return true;
   }
 
+  private boolean holdRepaint = false;
+
+  /**
+   * Called by IdCanvas and SeqPanel to defer painting until after JVP loading.
+   * 
+   * @return true if holding
+   */
+  public boolean getHoldRepaint()
+  {
+    return holdRepaint;
+  }
+
+  /**
+   * Called by Jalview2xml while loading
+   * 
+   * @param tf
+   */
+  public void setHoldRepaint(boolean tf)
+  {
+    if (holdRepaint == tf)
+    {
+      return;
+    }
+    holdRepaint = tf;
+    if (!tf)
+    {
+      repaint();
+    }
+  }
+
+  @Override
+  public void repaint()
+  {
+    if (holdRepaint)
+    {
+      // System.out.println("AP repaint holding");
+      // Platform.stackTrace();
+      return;
+    }
+    super.repaint();
+  }
+
+  public void selectAllSequences()
+  {
+    selectSequences(av.getAlignment().getSequences());
+  }
+
+  public void deselectAllSequences()
+  {
+    if (av.cursorMode)
+    {
+      getSeqPanel().keyboardNo1 = null;
+      getSeqPanel().keyboardNo2 = null;
+    }
+    av.setSelectionGroup(null);
+    av.getColumnSelection().clear();
+    av.setSelectionGroup(null);
+    getIdPanel().getIdCanvas().searchResults = null;
+    av.sendSelection();
+    // JAL-2034 - should delegate to
+    // alignPanel to decide if overview needs
+    // updating.
+    paintAlignment(false, false);
+    PaintRefresher.Refresh(this, av.getSequenceSetId());
+  }
+
+  public void selectSequences(List<SequenceI> seqs)
+  {
+    SequenceGroup sg = new SequenceGroup(seqs);
+    sg.setEndRes(av.getAlignment().getWidth() - 1);
+    av.setSelectionGroup(sg);
+    av.isSelectionGroupChanged(true);
+    av.sendSelection();
+    // JAL-2034 - should delegate to
+    // alignPanel to decide if overview needs
+    // updating.
+    paintAlignment(false, false);
+    PaintRefresher.Refresh(this, av.getSequenceSetId());
+  }
+
 }
index 791421d..233f280 100644 (file)
@@ -605,8 +605,8 @@ public class AnnotationChooser extends JPanel
     frame.setContentPane(this);
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
     Desktop.addInternalFrame(frame,
-            MessageManager.getString("label.choose_annotations"),
-            MY_FRAME_WIDTH, MY_FRAME_HEIGHT, true);
+            MessageManager.getString("label.choose_annotations"), Desktop.FRAME_MAKE_VISIBLE,
+            MY_FRAME_WIDTH, MY_FRAME_HEIGHT, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_SET_MIN_SIZE_300);
   }
 
   protected void setShowSelected(boolean showSelected)
index e89c1c2..bd05be1 100644 (file)
@@ -301,7 +301,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter
         updateView();
       }
     };
-    JalviewColourChooser.showColourChooser(Desktop.getDesktop(), ttl,
+    JalviewColourChooser.showColourChooser(Desktop.getDesktopPane(), ttl,
             colourPanel.getBackground(), listener);
   }
 
index d84287f..541c63a 100644 (file)
@@ -109,8 +109,8 @@ public class AnnotationExporter extends JPanel
     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);
   }
 
   /**
index 5a681f1..c843f37 100755 (executable)
  */
 package jalview.gui;
 
-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 java.awt.Color;
 import java.awt.Cursor;
 import java.awt.Dimension;
@@ -62,6 +47,21 @@ 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;
+
 /**
  * The panel that holds the labels for alignment annotations, providing
  * tooltips, context menus, drag to reorder rows, and drag to adjust panel
@@ -971,7 +971,7 @@ public class AnnotationLabels extends JPanel
             seqs, omitHidden, alignmentStartEnd);
 
     Toolkit.getDefaultToolkit().getSystemClipboard()
-            .setContents(new StringSelection(output), Desktop.instance);
+            .setContents(new StringSelection(output), Desktop.getInstance());
 
     HiddenColumns hiddenColumns = null;
 
@@ -981,20 +981,12 @@ public class AnnotationLabels extends JPanel
               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 };
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param g1
-   *          DOCUMENT ME!
-   */
   @Override
   public void paintComponent(Graphics g)
   {
index 35ae242..01be3b5 100755 (executable)
@@ -1009,23 +1009,28 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     g.setColor(Color.white);
     g.fillRect(0, 0, visibleRect.width, visibleRect.height);
 
+    ViewportRanges ranges = av.getRanges();
+    
+    
+    
     if (image != null)
     {
        // BH 2018 optimizing generation of new Rectangle().
       if (fastPaint || (visibleRect.width != (clipBounds = g.getClipBounds(clipBounds)).width)
             || (visibleRect.height != clipBounds.height))
       {
-
-         
          g.drawImage(image, 0, 0, this);
         fastPaint = false;
         return;
       }
     }
-    imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes()
+    
+    imgWidth = (ranges.getEndRes() - ranges.getStartRes()
             + 1) * av.getCharWidth();
+    
     if (imgWidth < 1)
     {
+      fastPaint = false;
       return;
     }
     Graphics2D gg;
@@ -1068,8 +1073,8 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
         gg = (Graphics2D) image.getGraphics();
 
     }
-    
-    drawComponent(gg, av.getRanges().getStartRes(),
+   
+    drawComponent(gg, ranges.getStartRes(),
             av.getRanges().getEndRes() + 1);
     gg.dispose();
     imageFresh = false;
@@ -1077,11 +1082,6 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   }
 
   /**
-   * set true to enable redraw timing debug output on stderr
-   */
-  private final boolean debugRedraw = false;
-
-  /**
    * non-Thread safe repaint
    * 
    * @param horizontal
@@ -1101,6 +1101,11 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     int sr = av.getRanges().getStartRes();
     int er = av.getRanges().getEndRes() + 1;
     int transX = 0;
+    
+    if (er == sr + 1) {
+      fastPaint = false;
+      return;
+    }
 
     Graphics2D gg = (Graphics2D) image.getGraphics();
 
index e13df4a..afb727f 100644 (file)
@@ -546,7 +546,7 @@ public class AppJmol extends StructureViewerBase
     }
     if (errormsgs.length() > 0)
     {
-      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+      JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
               MessageManager.formatMessage(
                       "label.pdb_entries_couldnt_be_retrieved", new String[]
                       { errormsgs.toString() }),
index fe0aedf..5b5bd18 100644 (file)
  */
 package jalview.gui;
 
-import jalview.api.StructureSelectionManagerProvider;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
-import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
 
-import javax.swing.JOptionPane;
-
 /**
- * GUI related routines for associating PDB files with sequences
+ * GUI related routines for associating PDB files with sequences. A single
+ * static method.
  * 
  * @author JimP
  * 
@@ -39,58 +36,56 @@ import javax.swing.JOptionPane;
 public class AssociatePdbFileWithSeq
 {
 
+  private AssociatePdbFileWithSeq()
+  {
+    // inaccessible
+  }
+
   /**
-   * assocate the given PDB file with
+   * Associate the given PDB file name or URL with a sequence. Do not map
+   * mouse-over events.
    * 
-   * @param choice
+   * @param fileName
+   *          or URL
+   * @param type
+   *          will be DataType.FILE or DataType.URL
    * @param sequence
+   *          to associate
+   * @param prompt
+   *          true if the user should be asked what to do if the specified file
+   *          does not seem to contain PDB information (StructureChooser only)
+   * @return null if file is not found
    */
-  public PDBEntry associatePdbWithSeq(String choice, DataSourceType file,
-          SequenceI sequence, boolean prompt,
-          StructureSelectionManagerProvider ssmp)
+  public static PDBEntry associatePdbWithSeq(String fileName,
+          DataSourceType type, SequenceI sequence, boolean prompt)
   {
     PDBEntry entry = new PDBEntry();
     StructureFile pdbfile = null;
-    pdbfile = StructureSelectionManager.getStructureSelectionManager(ssmp)
+    pdbfile = Desktop.getStructureSelectionManager()
             .setMapping(false, new SequenceI[]
-            { sequence }, null, choice, file);
+            { sequence }, null, fileName, type);
     if (pdbfile == null)
     {
       // stacktrace already thrown so just return
       return null;
     }
-    if (pdbfile.getId() == null)
-    {
-      String reply = null;
-
-      if (prompt)
-      {
-        reply = JvOptionPane.showInternalInputDialog(Desktop.desktop,
-                MessageManager
-                        .getString("label.couldnt_find_pdb_id_in_file"),
-                MessageManager.getString("label.no_pdb_id_in_file"),
-                JvOptionPane.QUESTION_MESSAGE);
-      }
-      if (reply == null)
-      {
-        return null;
-      }
-
-      entry.setId(reply);
-    }
-    else
+    String id = pdbfile.getId();
+    if (id == null && (id = (prompt
+            ? JvOptionPane.showInternalInputDialog(Desktop.getDesktopPane(),
+                    MessageManager
+                            .getString("label.couldnt_find_pdb_id_in_file"),
+                    MessageManager.getString("label.no_pdb_id_in_file"),
+                    JvOptionPane.QUESTION_MESSAGE)
+            : null)) == null)
     {
-      entry.setId(pdbfile.getId());
+      return null;
     }
+    entry.setId(id);
     entry.setType(PDBEntry.Type.FILE);
-
-    if (pdbfile != null)
-    {
-      entry.setFile(choice);
-      sequence.getDatasetSequence().addPDBId(entry);
-      StructureSelectionManager.getStructureSelectionManager(ssmp)
-              .registerPDBEntry(entry);
-    }
+    entry.setFile(fileName);
+    sequence.getDatasetSequence().addPDBId(entry);
+    Desktop.getStructureSelectionManager()
+            .registerPDBEntry(entry);
     return entry;
   }
 }
index 5248306..c69d0a8 100644 (file)
@@ -319,7 +319,7 @@ public class CalculationChooser extends JPanel
       title = title + " (" + af.getViewport().getViewName() + ")";
     }
 
-    Desktop.addInternalFrame(frame, title, width, height, false);
+    Desktop.addInternalFrame(frame, title, Desktop.FRAME_MAKE_VISIBLE, width, height, Desktop.FRAME_NOT_RESIZABLE, Desktop.FRAME_SET_MIN_SIZE_300);
     calcChoicePanel.doLayout();
     revalidate();
     /*
index c6d6e97..317eff5 100644 (file)
@@ -336,7 +336,7 @@ public class ChimeraViewFrame extends StructureViewerBase
 
     if (!jmb.launchChimera())
     {
-      JvOptionPane.showMessageDialog(Desktop.desktop,
+      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
               MessageManager.getString("label.chimera_failed"),
               MessageManager.getString("label.error_loading_file"),
               JvOptionPane.ERROR_MESSAGE);
@@ -497,7 +497,7 @@ public class ChimeraViewFrame extends StructureViewerBase
     if (errormsgs.length() > 0)
     {
 
-      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+      JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
               MessageManager.formatMessage(
                       "label.pdb_entries_couldnt_be_retrieved", new Object[]
                       { errormsgs.toString() }),
index 43d7015..e38b55b 100644 (file)
@@ -158,7 +158,7 @@ public class ColourMenuHelper
             ActionListener al = radioItem.getActionListeners()[0];
             radioItem.removeActionListener(al);
             int option = JvOptionPane.showInternalConfirmDialog(
-                    Desktop.desktop,
+                    Desktop.getDesktopPane(),
                     MessageManager
                             .getString("label.remove_from_default_list"),
                     MessageManager
index 2ada4d2..cc25696 100644 (file)
@@ -454,7 +454,7 @@ public class CrossRefAction implements Runnable
             .setGapCharacter(alignFrame.viewport.getGapCharacter());
 
     StructureSelectionManager ssm = StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance);
+            .getStructureSelectionManager(Desktop.getInstance());
 
     /*
      * register any new mappings for sequence mouseover etc
index d328a0d..4badcba 100644 (file)
@@ -234,7 +234,7 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer
               .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);
@@ -253,7 +253,7 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer
 
     } 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"),
@@ -342,7 +342,7 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer
               .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);
index 0e2fcf0..3d91e47 100644 (file)
@@ -94,6 +94,9 @@ import org.stackoverflowusers.file.WindowsShortcut;
 
 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;
@@ -108,6 +111,7 @@ 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;
@@ -130,9 +134,11 @@ import jalview.ws.utils.UrlDownloadClient;
  * @author $author$
  * @version $Revision: 1.155 $
  */
-public class Desktop extends jalview.jbgui.GDesktop
+@SuppressWarnings("serial")
+public class Desktop extends GDesktop
         implements DropTargetListener, ClipboardOwner, IProgressIndicator,
-        jalview.api.StructureSelectionManagerProvider
+        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"
@@ -201,41 +207,47 @@ public class Desktop extends jalview.jbgui.GDesktop
             listener);
   }
 
-  /** Singleton Desktop instance */
-  public static Desktop instance;
+  private MyDesktopPane desktopPane;
+
+  public static MyDesktopPane getDesktopPane()
+  {
+    Desktop desktop = getInstance();
+    return desktop == null ? null : desktop.desktopPane;
+  }
 
   /**
-   * BH TEMPORARY ONLY -- should use ApplicationSingleton
+   * 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 instance;
+    return Jalview.isHeadlessMode() ? null
+            : (Desktop) ApplicationSingletonProvider
+                    .getInstance(Desktop.class);
   }
 
-  public static MyDesktopPane desktop;
-
-  public static MyDesktopPane getDesktop()
+  public static StructureSelectionManager getStructureSelectionManager()
   {
-    // BH 2018 could use currentThread() here as a reference to a
-    // Hashtable<Thread, MyDesktopPane> in JavaScript
-    return desktop;
+    return StructureSelectionManager
+            .getStructureSelectionManager(getInstance());
   }
 
-  static int openFrameCount = 0;
+  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
   {
@@ -256,7 +268,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       } catch (NullPointerException npe)
       {
         Point p = getMousePosition();
-        instance.showPasteMenu(p.x, p.y);
+        showPasteMenu(p.x, p.y);
       }
     }
 
@@ -304,14 +316,14 @@ public class Desktop extends jalview.jbgui.GDesktop
     public void endDraggingFrame(JComponent f)
     {
       delegate.endDraggingFrame(f);
-      desktop.repaint();
+      desktopPane.repaint();
     }
 
     @Override
     public void endResizingFrame(JComponent f)
     {
       delegate.endResizingFrame(f);
-      desktop.repaint();
+      desktopPane.repaint();
     }
 
     @Override
@@ -361,190 +373,202 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /**
-   * 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;
-
-    doConfigureStructurePrefs();
-    setTitle("Jalview " + Cache.getProperty("VERSION"));
-    /*
-    if (!Platform.isAMac())
-    {
-      // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
-    }
-    else
-    {
-     this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
-    }
-    */
-
+    Cache.initLogger();
     try
     {
-      APQHandlers.setAPQHandlers(this);
-    } catch (Throwable t)
-    {
-      System.out.println("Error setting APQHandlers: " + t.toString());
-      // t.printStackTrace();
-    }
-
-    addWindowListener(new WindowAdapter()
-    {
+      /**
+       * 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.
+       */
 
-      @Override
-      public void windowClosing(WindowEvent ev)
+      doConfigureStructurePrefs();
+      setTitle("Jalview " + Cache.getProperty("VERSION"));
+      /*
+      if (!Platform.isAMac())
       {
-        quit();
+      // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
       }
-    });
+      else
+      {
+       this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+      }
+      */
 
-    boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE",
-            false);
+      try
+      {
+        APQHandlers.setAPQHandlers(this);
+      } catch (Throwable t)
+      {
+        System.out.println("Error setting APQHandlers: " + t.toString());
+        // t.printStackTrace();
+      }
 
-    boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE",
-            false);
-    desktop = new MyDesktopPane(selmemusage);
+      addWindowListener(new WindowAdapter()
+      {
 
-    showMemusage.setSelected(selmemusage);
-    desktop.setBackground(Color.white);
+        @Override
+        public void windowClosing(WindowEvent ev)
+        {
+          quit();
+        }
+      });
 
-    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);
+      boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
 
-    // BH 2018 - just an experiment to try unclipped JInternalFrames.
-    if (Platform.isJS())
-    {
-      getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
-    }
+      boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
+      desktopPane = new MyDesktopPane(selmemusage);
 
-    getContentPane().add(desktop, BorderLayout.CENTER);
-    desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
+      showMemusage.setSelected(selmemusage);
+      desktopPane.setBackground(Color.white);
 
-    // 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())));
+      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);
 
-    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);
-    }
+      // BH 2018 - just an experiment to try unclipped JInternalFrames.
+      if (Platform.isJS())
+      {
+        getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
+      }
 
-    if (!Platform.isJS())
-    /**
-     * Java only
-     * 
-     * @j2sIgnore
-     */
-    {
-      jconsole = new Console(this, showjconsole);
-      jconsole.setHeader(Cache.getVersionDetailsForConsole());
-      showConsole(showjconsole);
+      getContentPane().add(desktopPane, BorderLayout.CENTER);
+      desktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
 
-      showNews.setVisible(false);
+      // 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())));
 
-      experimentalFeatures.setSelected(showExperimental());
+      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();
 
-      checkURLLinks();
+      if (!Platform.isJS())
+      /**
+       * Java only
+       * 
+       * @j2sIgnore
+       */
+      {
+        jconsole = new Console(this, showjconsole);
+        jconsole.setHeader(Cache.getVersionDetailsForConsole());
+        showConsole(showjconsole);
 
-      // Spawn a thread that shows the splashscreen
+        showNews.setVisible(false);
 
-      SwingUtilities.invokeLater(new Runnable()
-      {
-        @Override
-        public void run()
-        {
-          new SplashScreen(true);
-        }
-      });
+        experimentalFeatures.setSelected(showExperimental());
 
-      // 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()
+        if (Jalview.isInteractive())
         {
-          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()
-              {
+          // disabled for SeqCanvasTest
+          checkURLLinks();
 
-                @Override
-                public void propertyChange(PropertyChangeEvent evt)
-                {
-                  Cache.log.debug("Firing service changed event for "
-                          + evt.getNewValue());
-                  JalviewServicesChanged(evt);
-                }
-              });
-    }
+          checkURLLinks();
 
-    this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
+          // Spawn a thread that shows the splashscreen
 
-    this.addWindowListener(new WindowAdapter()
-    {
-      @Override
-      public void windowClosing(WindowEvent evt)
-      {
-        quit();
-      }
-    });
+          SwingUtilities.invokeLater(new Runnable()
+          {
+            @Override
+            public void run()
+            {
+              new SplashScreen(true);
+            }
+          });
 
-    MouseAdapter ma;
-    this.addMouseListener(ma = new MouseAdapter()
-    {
-      @Override
-      public void mousePressed(MouseEvent evt)
-      {
-        if (evt.isPopupTrigger()) // Mac
-        {
-          showPasteMenu(evt.getX(), evt.getY());
+          // 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);
+                    }
+                  });
         }
-      }
 
-      @Override
-      public void mouseReleased(MouseEvent evt)
-      {
-        if (evt.isPopupTrigger()) // Windows
+        this.setDropTarget(new java.awt.dnd.DropTarget(desktopPane, this));
+
+        this.addWindowListener(new WindowAdapter()
         {
-          showPasteMenu(evt.getX(), evt.getY());
-        }
+          @Override
+          public void windowClosing(WindowEvent evt)
+          {
+            quit();
+          }
+        });
+
+        MouseAdapter ma;
+        this.addMouseListener(ma = new MouseAdapter()
+        {
+          @Override
+          public void mousePressed(MouseEvent evt)
+          {
+            if (evt.isPopupTrigger()) // Mac
+            {
+              showPasteMenu(evt.getX(), evt.getY());
+            }
+          }
+
+          @Override
+          public void mouseReleased(MouseEvent evt)
+          {
+            if (evt.isPopupTrigger()) // Windows
+            {
+              showPasteMenu(evt.getX(), evt.getY());
+            }
+          }
+        });
+        desktopPane.addMouseListener(ma);
       }
-    });
-    desktop.addMouseListener(ma);
+    } catch (Throwable t)
+    {
+      t.printStackTrace();
+    }
 
   }
 
@@ -568,10 +592,10 @@ public class Desktop extends jalview.jbgui.GDesktop
             .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.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));
     }
@@ -620,7 +644,7 @@ public class Desktop extends jalview.jbgui.GDesktop
         }
       }
     }).start();
-    
+
   }
 
   @Override
@@ -641,10 +665,10 @@ public class Desktop extends jalview.jbgui.GDesktop
         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();
@@ -665,10 +689,8 @@ public class Desktop extends jalview.jbgui.GDesktop
     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");
+    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),
@@ -678,10 +700,10 @@ public class Desktop extends jalview.jbgui.GDesktop
         // 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);
@@ -752,69 +774,81 @@ public class Desktop extends jalview.jbgui.GDesktop
     }
   }
 
-  /**
-   * 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 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);
+//  }
 
   /**
-   * Add an internal frame to the Jalview desktop
+   * 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 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
-   * 
-   * @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)
+          final JInternalFrame frame, String title, int w, int h)
   {
-    addInternalFrame(frame, title, true, w, h, resizable, false);
+    // 58 classes
+    getInstance().addFrame(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
@@ -836,7 +870,29 @@ public class Desktop extends jalview.jbgui.GDesktop
           final JInternalFrame frame, String title, boolean makeVisible,
           int w, int h, boolean resizable, boolean ignoreMinSize)
   {
+    // 15 classes
+    getInstance().addFrame(frame, title, makeVisible, w, h, 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, String title,
+          boolean makeVisible, int w, int h, boolean resizable,
+          boolean ignoreMinSize)
+  {
     // 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
@@ -851,19 +907,20 @@ public class Desktop extends jalview.jbgui.GDesktop
     // 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")))
+    if (Jalview.isHeadlessMode())
     {
       return;
     }
 
     openFrameCount++;
-
+    
+    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)
     {
-      frame.setMinimumSize(
-              new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
-
       // Set default dimension for Alignment Frame window.
       // The Alignment Frame window could be added from a number of places,
       // hence,
@@ -872,6 +929,10 @@ public class Desktop extends jalview.jbgui.GDesktop
       {
         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));
+        
       }
     }
 
@@ -881,7 +942,6 @@ public class Desktop extends jalview.jbgui.GDesktop
     frame.setMaximizable(resizable);
     frame.setIconifiable(resizable);
     frame.setOpaque(Platform.isJS());
-    boolean isEmbedded = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
     if (!isEmbedded && frame.getX() < 1 && frame.getY() < 1)
     {
       frame.setLocation(xOffset * openFrameCount,
@@ -898,7 +958,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       @Override
       public void internalFrameActivated(InternalFrameEvent evt)
       {
-        JInternalFrame itf = desktop.getSelectedFrame();
+        JInternalFrame itf = getDesktopPane().getSelectedFrame();
         if (itf != null)
         {
           if (itf instanceof AlignFrame)
@@ -930,7 +990,7 @@ public class Desktop extends jalview.jbgui.GDesktop
         {
           menuItem.removeActionListener(menuItem.getActionListeners()[0]);
         }
-        windowMenu.remove(menuItem);
+        getInstance().windowMenu.remove(menuItem);
       }
     });
 
@@ -952,9 +1012,9 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     setKeyBindings(frame);
 
-    desktop.add(frame);
+    getDesktopPane().add(frame);
 
-    windowMenu.add(menuItem);
+    getInstance().windowMenu.add(menuItem);
 
     frame.toFront();
     try
@@ -972,14 +1032,13 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /**
-   * 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()
     {
       @Override
@@ -1012,7 +1071,7 @@ public class Desktop extends jalview.jbgui.GDesktop
   {
     if (!internalCopy)
     {
-      Desktop.jalviewClipboard = null;
+      jalviewClipboard = null;
     }
 
     internalCopy = false;
@@ -1057,7 +1116,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     try
     {
-      Desktop.transferFromDropTarget(files, protocols, evt, t);
+      transferFromDropTarget(files, protocols, evt, t);
     } catch (Exception e)
     {
       e.printStackTrace();
@@ -1113,8 +1172,9 @@ public class Desktop extends jalview.jbgui.GDesktop
   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(
@@ -1247,7 +1307,7 @@ public class Desktop extends jalview.jbgui.GDesktop
           {
             String msg = MessageManager
                     .formatMessage("label.couldnt_locate", url);
-            JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
+            JvOptionPane.showInternalMessageDialog(getDesktopPane(), msg,
                     MessageManager.getString("label.url_not_found"),
                     JvOptionPane.WARNING_MESSAGE);
 
@@ -1268,7 +1328,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     };
     String dialogOption = MessageManager
             .getString("label.input_alignment_from_url");
-    JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
+    JvOptionPane.newOptionDialog(desktopPane).setResponseHandler(0, action)
             .showInternalDialog(panel, dialogOption,
                     JvOptionPane.YES_NO_CANCEL_OPTION,
                     JvOptionPane.PLAIN_MESSAGE, null, options,
@@ -1288,9 +1348,10 @@ public class Desktop extends jalview.jbgui.GDesktop
   {
     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);
   }
 
   /*
@@ -1300,10 +1361,8 @@ public class Desktop extends jalview.jbgui.GDesktop
   public void quit()
   {
     Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
-    Cache.setProperty("SCREENGEOMETRY_WIDTH",
-            screen.width + "");
-    Cache.setProperty("SCREENGEOMETRY_HEIGHT",
-            screen.height + "");
+    Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
+    Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
     storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
             getWidth(), getHeight()));
 
@@ -1334,9 +1393,9 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   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.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 + "");
@@ -1447,7 +1506,7 @@ public class Desktop extends jalview.jbgui.GDesktop
   public void closeAll_actionPerformed(ActionEvent e)
   {
     // TODO show a progress bar while closing?
-    JInternalFrame[] frames = desktop.getAllFrames();
+    JInternalFrame[] frames = desktopPane.getAllFrames();
     for (int i = 0; i < frames.length; i++)
     {
       try
@@ -1514,7 +1573,7 @@ public class Desktop extends jalview.jbgui.GDesktop
   @Override
   protected void showMemusage_actionPerformed(ActionEvent e)
   {
-    desktop.showMemoryUsage(showMemusage.isSelected());
+    desktopPane.showMemoryUsage(showMemusage.isSelected());
   }
 
   /*
@@ -1551,7 +1610,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   void reorderAssociatedWindows(boolean minimize, boolean close)
   {
-    JInternalFrame[] frames = desktop.getAllFrames();
+    JInternalFrame[] frames = desktopPane.getAllFrames();
     if (frames == null || frames.length < 1)
     {
       return;
@@ -1691,16 +1750,17 @@ public class Desktop extends jalview.jbgui.GDesktop
           setProgressBar(MessageManager.formatMessage(
                   "label.saving_jalview_project", new Object[]
                   { chosenFile.getName() }), chosenFile.hashCode());
-          Cache.setProperty("LAST_DIRECTORY",
-                  chosenFile.getParent());
+          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
           {
-               boolean doBackup = BackupFiles.getEnabled();
-            BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
+            boolean doBackup = BackupFiles.getEnabled();
+            BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
+                    : null;
 
-            new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
+            new Jalview2XML().saveState(
+                    doBackup ? backupfiles.getTempFile() : chosenFile);
 
             if (doBackup)
             {
@@ -1726,7 +1786,7 @@ public class Desktop extends jalview.jbgui.GDesktop
           setProgressBar(null, chosenFile.hashCode());
         }
       }).start();
-      }
+    }
   }
 
   @Override
@@ -1757,8 +1817,10 @@ public class Desktop extends jalview.jbgui.GDesktop
         "Jalview Project (old)" };
     JalviewFileChooser chooser = new JalviewFileChooser(
             Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
-            "Jalview Project", true, BackupFiles.getEnabled()); // last two booleans: allFiles,
-                                            // allowBackupFiles
+            "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()
@@ -1775,29 +1837,30 @@ public class Desktop extends jalview.jbgui.GDesktop
           @Override
           public void run()
           {
-               try 
+            try
             {
               new Jalview2XML().loadJalviewAlign(selectedFile);
             } 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);
-               }
+            {
+              new OOMWarning("Whilst loading project from " + choice, oom);
+            } 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();
       }
     });
-    
+
     chooser.showOpenDialog(this);
   }
 
@@ -1828,7 +1891,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     {
       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();
@@ -1841,7 +1904,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     ((GridLayout) progressPanel.getLayout()).setRows(
             ((GridLayout) progressPanel.getLayout()).getRows() + 1);
     ++totalProgressCount;
-    instance.validate();
+    validate();
     return thisprogress;
   }
 
@@ -1895,7 +1958,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    */
   public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
   {
-    if (Desktop.desktop == null)
+    if (getDesktopPane() == null)
     {
       // no frames created and in headless mode
       // TODO: verify that frames are recoverable when in headless mode
@@ -1937,9 +2000,9 @@ public class Desktop extends jalview.jbgui.GDesktop
   public static AlignmentViewport[] getViewports(String sequenceSetId)
   {
     List<AlignmentViewport> viewp = new ArrayList<>();
-    if (desktop != null)
+    if (getDesktopPane() != null)
     {
-      AlignFrame[] frames = Desktop.getAlignFrames();
+      AlignFrame[] frames = getAlignFrames();
 
       for (AlignFrame afr : frames)
       {
@@ -1986,9 +2049,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     // FIXME: ideally should use UI interface API
     FeatureSettings viewFeatureSettings = (af.featureSettings != null
-            && af.featureSettings.isOpen())
-            ? af.featureSettings
-            : null;
+            && af.featureSettings.isOpen()) ? af.featureSettings : null;
     Rectangle fsBounds = af.getFeatureSettingsGeometry();
     for (int i = 0; i < size; i++)
     {
@@ -2021,7 +2082,8 @@ public class Desktop extends jalview.jbgui.GDesktop
 
       addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
               AlignFrame.DEFAULT_HEIGHT);
-      // and materialise a new feature settings dialog instance for the new alignframe
+      // 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()
@@ -2039,9 +2101,9 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   /**
    * 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
    */
@@ -2049,7 +2111,7 @@ public class Desktop extends jalview.jbgui.GDesktop
   {
     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++)
     {
@@ -2093,8 +2155,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     }
 
     // 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();
     }
@@ -2102,7 +2163,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   public JInternalFrame[] getAllFrames()
   {
-    return desktop.getAllFrames();
+    return desktopPane.getAllFrames();
   }
 
   /**
@@ -2188,7 +2249,7 @@ public class Desktop extends jalview.jbgui.GDesktop
           });
           msgPanel.add(jcb);
 
-          JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
+          JvOptionPane.showMessageDialog(desktopPane, msgPanel,
                   MessageManager
                           .getString("label.SEQUENCE_ID_no_longer_used"),
                   JvOptionPane.WARNING_MESSAGE);
@@ -2199,12 +2260,12 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   /**
    * 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;
 
@@ -2303,11 +2364,10 @@ public class Desktop extends jalview.jbgui.GDesktop
   {
     if (Jalview.isHeadlessMode())
     {
-      // Desktop.desktop is null in headless mode
-      return new AlignFrame[] { Jalview.currentAlignFrame };
+      return new AlignFrame[] { Jalview.getInstance().currentAlignFrame };
     }
 
-    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+    JInternalFrame[] frames = getDesktopPane().getAllFrames();
 
     if (frames == null)
     {
@@ -2352,7 +2412,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    */
   public GStructureViewer[] getJmols()
   {
-    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+    JInternalFrame[] frames = desktopPane.getAllFrames();
 
     if (frames == null)
     {
@@ -2388,7 +2448,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     } 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"),
@@ -2441,14 +2501,23 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /**
-   * 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()
   {
+<<<<<<< HEAD
+    getRootPane()
+            .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+                    KeyStroke
+                            .getKeyStroke(KeyEvent.VK_Q,
+                                    jalview.util.ShortcutKeyMaskExWrapper
+                                            .getMenuShortcutKeyMaskEx()),
+=======
     getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
             .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
                     Platform.SHORTCUT_KEY_MASK),
+>>>>>>> refs/heads/Jalview-JS/develop.JAL-3446.ctrlDown
                     "Quit");
     getRootPane().getActionMap().put("Quit", new AbstractAction()
     {
@@ -2577,15 +2646,16 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /**
-   * 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)
+    if (getDesktopPane() != null)
     {
       AlignmentPanel[] aps = getAlignmentPanels(
               viewport.getSequenceSetId());
@@ -2647,7 +2717,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       // todo: changesupport handlers need to be transferred
       if (discoverer == null)
       {
-        discoverer = new jalview.ws.jws1.Discoverer();
+        discoverer = jalview.ws.jws1.Discoverer.getInstance();
         // register PCS handler for desktop.
         discoverer.addPropertyChangeListener(changeSupport);
       }
@@ -2658,7 +2728,7 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
     {
-      t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
+      t2 = jalview.ws.jws2.Jws2Discoverer.getInstance()
               .startDiscoverer(changeSupport);
     }
     Thread t3 = null;
@@ -2691,7 +2761,7 @@ public class Desktop extends jalview.jbgui.GDesktop
   {
     if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
     {
-      final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer()
+      final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
               .getErrorMessages();
       if (ermsg != null)
       {
@@ -2730,7 +2800,7 @@ public class Desktop extends jalview.jbgui.GDesktop
                  * 
                  * jd.waitForInput();
                  */
-                JvOptionPane.showConfirmDialog(Desktop.desktop,
+                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,"
@@ -2767,7 +2837,7 @@ public class Desktop extends jalview.jbgui.GDesktop
    */
   public static void showUrl(final String url)
   {
-    showUrl(url, Desktop.instance);
+    showUrl(url, getInstance());
   }
 
   /**
@@ -2796,7 +2866,7 @@ public class Desktop extends jalview.jbgui.GDesktop
           jalview.util.BrowserLauncher.openURL(url);
         } catch (Exception ex)
         {
-          JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+          JvOptionPane.showInternalMessageDialog(getDesktopPane(),
                   MessageManager
                           .getString("label.web_browser_not_found_unix"),
                   MessageManager.getString("label.web_browser_not_found"),
@@ -2836,7 +2906,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       try
       {
         url = e.getURL().toString();
-        Desktop.showUrl(url);
+        showUrl(url);
       } catch (Exception x)
       {
         if (url != null)
@@ -2895,7 +2965,7 @@ public class Desktop extends jalview.jbgui.GDesktop
           {
           }
         }
-        if (instance == null)
+        if (Jalview.isHeadlessMode())
         {
           return;
         }
@@ -2955,8 +3025,8 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   /**
    * 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.
    * 
@@ -3021,7 +3091,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       {
         splitFrame.setLocation(geometry.getLocation());
       }
-      Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
+      addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
     }
 
     /*
@@ -3059,7 +3129,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     String topViewId = myTopFrame.viewport.getSequenceSetId();
     String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
 
-    JInternalFrame[] frames = desktop.getAllFrames();
+    JInternalFrame[] frames = desktopPane.getAllFrames();
     for (JInternalFrame frame : frames)
     {
       if (frame instanceof SplitFrame && frame != source)
@@ -3122,12 +3192,14 @@ public class Desktop extends jalview.jbgui.GDesktop
    *          - the payload from the drop event
    * @throws Exception
    */
+  @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
+    // 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++) {
@@ -3139,7 +3211,8 @@ public class Desktop extends jalview.jbgui.GDesktop
     // byte[] data = getDroppedFileBytes(file);
     // fileName.setText(file.getName() + " - " + data.length + " " +
     // evt.getLocation());
-    // JTextArea target = (JTextArea) ((DropTarget) evt.getSource()).getComponent();
+    // JTextArea target = (JTextArea) ((DropTarget)
+    // evt.getSource()).getComponent();
     // target.setText(new String(data));
     // }
     // dtde.dropComplete(true);
@@ -3191,7 +3264,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     {
       // Works on Windows and MacOSX
       Cache.log.debug("Drop handled as javaFileListFlavor");
-      for (Object file : (List) t
+      for (File file : (List<File>) t
               .getTransferData(DataFlavor.javaFileListFlavor))
       {
         files.add(file);
@@ -3339,10 +3412,10 @@ public class Desktop extends jalview.jbgui.GDesktop
   }
 
   /**
-   * 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
@@ -3355,7 +3428,7 @@ public class Desktop extends jalview.jbgui.GDesktop
           Class<? extends StructureViewerBase> structureViewerClass)
   {
     List<StructureViewerBase> result = new ArrayList<>();
-    JInternalFrame[] frames = Desktop.instance.getAllFrames();
+    JInternalFrame[] frames = getAllFrames();
 
     for (JInternalFrame frame : frames)
     {
index d547c58..a02ec36 100644 (file)
@@ -222,7 +222,7 @@ public class FeatureEditor
               updateColourButton(mainPanel, colour, featureColour);
             };
           };
-          JalviewColourChooser.showColourChooser(Desktop.getDesktop(),
+          JalviewColourChooser.showColourChooser(Desktop.getDesktopPane(),
                   title, featureColour.getColour(), listener);
         }
         else
@@ -412,7 +412,7 @@ public class FeatureEditor
      * set dialog action handlers for OK (create/Amend) and Cancel options
      * also for Delete if applicable (when amending features)
      */
-    JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop)
+    JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.getDesktopPane())
             .setResponseHandler(0, okAction).setResponseHandler(2, cancelAction);
     if (!forCreate)
     {
index b49593a..8c4b355 100644 (file)
@@ -436,8 +436,8 @@ public class FeatureSettings extends JPanel
       }
       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);
       }
index a1693f7..01c8a16 100755 (executable)
@@ -121,8 +121,8 @@ public class Finder extends GFinder
               }
             });
     addEscapeHandler();
-    Desktop.addInternalFrame(frame, MessageManager.getString("label.find"),
-            true, MY_WIDTH, MY_HEIGHT, true, true);
+    Desktop.addInternalFrame(frame, MessageManager.getString("label.find"), 
+            Desktop.FRAME_MAKE_VISIBLE, MY_WIDTH, MY_HEIGHT, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_ALLOW_ANY_SIZE);
     searchBox.getComponent().requestFocus();
   }
 
@@ -176,7 +176,7 @@ public class Finder extends GFinder
    */
   boolean getFocusedViewport()
   {
-    if (focusfixed || Desktop.desktop == null)
+    if (focusfixed || Desktop.getDesktopPane() == null)
     {
       if (ap != null && av != null)
       {
@@ -187,7 +187,7 @@ public class Finder extends GFinder
     }
     // 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];
index 92cc4c6..2d9682b 100755 (executable)
@@ -137,14 +137,14 @@ public class FontChooser extends GFontChooser
     if (isTreeFont())
     {
       Desktop.addInternalFrame(frame,
-              MessageManager.getString("action.change_font_tree_panel"),
-              400, 200, false);
+              MessageManager.getString("action.change_font_tree_panel"), Desktop.FRAME_MAKE_VISIBLE,
+              400, 200, Desktop.FRAME_NOT_RESIZABLE, Desktop.FRAME_SET_MIN_SIZE_300);
     }
     else
     {
       Desktop.addInternalFrame(frame,
-              MessageManager.getString("action.change_font"), 380, 220,
-              false);
+              MessageManager.getString("action.change_font"), Desktop.FRAME_MAKE_VISIBLE, 380, 220,
+              Desktop.FRAME_NOT_RESIZABLE, Desktop.FRAME_SET_MIN_SIZE_300);
     }
 
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
index 10c0787..14ae033 100755 (executable)
  */
 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;
@@ -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 @@ public class IdCanvas extends JPanel implements ViewportListenerI
 
   int imgHeight = 0;
 
-  boolean fastPaint = false;
+  private boolean fastPaint = false;
 
   List<SequenceI> searchResults;
 
@@ -137,6 +137,7 @@ public class IdCanvas extends JPanel implements ViewportListenerI
     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);
@@ -221,6 +222,11 @@ public class IdCanvas extends JPanel implements ViewportListenerI
   @Override
   public void paintComponent(Graphics g)
   {
+    if (av.getAlignPanel().getHoldRepaint())
+    {
+      return;
+    }
+
     g.setColor(Color.white);
     g.fillRect(0, 0, getWidth(), getHeight());
     
@@ -244,7 +250,7 @@ public class IdCanvas extends JPanel implements ViewportListenerI
     
     if (oldHeight != imgHeight || image.getWidth(this) != getWidth())
     {
-       image = new BufferedImage(getWidth(), imgHeight,
+      image = new BufferedImage(getWidth(), imgHeight,
                 BufferedImage.TYPE_INT_RGB);
     }
     
@@ -384,20 +390,49 @@ public class IdCanvas extends JPanel implements ViewportListenerI
     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();
@@ -568,20 +603,34 @@ public class IdCanvas extends JPanel implements ViewportListenerI
   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()
+  {
+    fastPaint = false;
+  }
 }
index 4b5e9d4..2734b8d 100755 (executable)
@@ -242,7 +242,7 @@ public class IdPanel extends JPanel
       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);
index 1d7bf3d..0ff5606 100644 (file)
@@ -78,11 +78,11 @@ public abstract class JalviewDialog extends JPanel
           boolean block, String title, int width, int height)
   {
 
-    frame = new JDialog(Desktop.instance, modal);
+    frame = new JDialog(Desktop.getInstance(), modal);
     frame.setTitle(title);
-    if (Desktop.instance != null)
+    if (Desktop.getInstance() != null)
     {
-      Rectangle deskr = Desktop.instance.getBounds();
+      Rectangle deskr = Desktop.getInstance().getBounds();
       frame.setBounds(new Rectangle((int) (deskr.getCenterX() - width / 2),
               (int) (deskr.getCenterY() - height / 2), width, height));
     }
index 2f4a0fe..f23dcf8 100644 (file)
@@ -47,6 +47,7 @@ import javax.swing.border.Border;
 import javax.swing.border.TitledBorder;
 
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 
 /**
  * useful functions for building Swing GUIs
@@ -56,12 +57,18 @@ import jalview.util.MessageManager;
  */
 public final class JvSwingUtils
 {
+  static final String HTML_PREFIX = (Platform.isJS() ? 
+          "<html><div style=\"max-width:350px;overflow-wrap:break-word;display:inline-block\">"
+          : "<html><div style=\"width:350; text-align: justify; word-wrap: break-word;\">"
+            );
+
   /**
    * wrap a bare html safe string to around 60 characters per line using a CSS
    * style class specifying word-wrap and break-word
    * 
    * @param enclose
-   *          if true, add &lt;html&gt; wrapper tags
+   *          if true, add &lt;html&gt; wrapper tags (currently false for only
+   *          two references -- both in Jws2Discoverer --
    * @param ttext
    * 
    * @return
@@ -70,40 +77,40 @@ public final class JvSwingUtils
   {
     Objects.requireNonNull(ttext,
             "Tootip text to format must not be null!");
-    ttext = ttext.trim();
-    boolean maxLengthExceeded = false;
+    ttext = ttext.trim().replaceAll("<br/>", "<br>");
 
-    if (ttext.contains("<br>"))
+    boolean maxLengthExceeded = false;
+    boolean isHTML = ttext.startsWith("<html>");
+    if (isHTML)
     {
-      String[] htmllines = ttext.split("<br>");
-      for (String line : htmllines)
-      {
-        maxLengthExceeded = line.length() > 60;
-        if (maxLengthExceeded)
-        {
+      ttext = ttext.substring(6);
+    }
+    if (ttext.endsWith("</html>"))
+    {
+      isHTML = true;
+      ttext = ttext.substring(0, ttext.length() - 7);
+    }
+    boolean hasBR = ttext.contains("<br>");
+    enclose |= isHTML || hasBR;
+    if (hasBR)
+    {  
+      int pt = -1, ptlast = -4;
+      while ((pt = ttext.indexOf("<br>", pt + 1)) >= 0) {
+        if (pt - ptlast - 4 > 60) {
+          maxLengthExceeded = true;
           break;
         }
       }
     }
-    else
+    else  
     {
       maxLengthExceeded = ttext.length() > 60;
     }
 
-    if (!maxLengthExceeded)
-    {
-      return enclose ? "<html>" + ttext + "</html>" : ttext;
-    }
-
-    return (enclose ? "<html>" : "")
-     // BH 2018
-            + "<style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style><div class=\"ttip\">"
-//            + "<style> p.ttip {width:350px;margin:-14px 0px -14px 0px;padding:2px;overflow-wrap:break-word;}"
-//            + "</style><p class=\"ttip\">"
-            + ttext
-            + " </div>"
-//            + "</p>"
-            + ((enclose ? "</html>" : ""));
+    String ret = (!enclose ? ttext : maxLengthExceeded ? HTML_PREFIX + ttext + "</div></html>" :
+      "<html>" + ttext + "</html>");
+    //System.out.println("JvSwUtil " + enclose + " " + maxLengthExceeded + " " + ret);
+    return ret;
   }
 
   public static JButton makeButton(String label, String tooltip,
index 9704b39..62bdd35 100644 (file)
@@ -86,7 +86,7 @@ public class LineartOptions extends JPanel
       ex.printStackTrace();
     }
 
-    dialog = JvOptionPane.newOptionDialog(Desktop.desktop);
+    dialog = JvOptionPane.newOptionDialog(Desktop.getDesktopPane());
   }
 
   /**
index 02c8fe1..dc5d0f5 100644 (file)
@@ -72,7 +72,7 @@ public class OOMWarning implements Runnable
 
   public OOMWarning(String string, OutOfMemoryError oomerror)
   {
-    this(string, oomerror, Desktop.desktop);
+    this(string, oomerror, Desktop.getDesktopPane());
   }
 
   @Override
index 953fdc5..326bac0 100755 (executable)
@@ -128,13 +128,13 @@ public class PaintRefresher
       {
         // BH 2019.04.22 fixes JS problem of repaint() consolidation
         // that occurs in JavaScript but not Java [JAL-3226]
-        ((IdCanvas) comp).fastPaint = false;
+        ((IdCanvas) comp).setNoFastPaint();
       }
       else if (comp instanceof SeqCanvas)
       {
         // BH 2019.04.22 fixes JS problem of repaint() consolidation
         // that occurs in JavaScript but not Java [JAL-3226]
-        ((SeqCanvas) comp).fastPaint = false;
+        ((SeqCanvas) comp).setNoFastPaint();
       }
       comp.repaint();
     }
index 2a7fb9f..4469b43 100644 (file)
@@ -301,7 +301,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       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);
@@ -1699,7 +1699,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       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());
@@ -2040,8 +2040,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
                   ap.paintAlignment(false, false);
                 }
                 sequence.setDescription(dialog.getDescription());
-                ap.av.firePropertyChange("alignment", null,
-                        ap.av.getAlignment().getSequences());
+                ap.av.notifyAlignment();
               }
             });
   }
@@ -2083,7 +2082,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         refresh();
       }
     };
-    JalviewColourChooser.showColourChooser(Desktop.getDesktop(),
+    JalviewColourChooser.showColourChooser(Desktop.getDesktopPane(),
             title, Color.BLUE, listener);
   }
 
@@ -2172,9 +2171,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
               startEnd, caseChange);
 
       ap.alignFrame.addHistoryItem(caseCommand);
+      ap.av.notifyAlignment();
 
-      ap.av.firePropertyChange("alignment", null,
-              ap.av.getAlignment().getSequences());
 
     }
   }
@@ -2281,8 +2279,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
                           sg.getStartRes(), sg.getEndRes() + 1,
                           ap.av.getAlignment());
                   ap.alignFrame.addHistoryItem(editCommand);
-                  ap.av.firePropertyChange("alignment", null,
-                          ap.av.getAlignment().getSequences());
+                  ap.av.notifyAlignment();
                 }
               });
     }
index 55345e0..4030afa 100755 (executable)
@@ -781,16 +781,16 @@ public class Preferences extends GPreferences
             protColour.getSelectedItem().toString());
     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 Overview settings
      */
-    Cache.setColourProperty(GAP_COLOUR, gapColour.getBackground());
-    Cache.setColourProperty(HIDDEN_COLOUR, hiddenColour.getBackground());
+    Cache.setColourPropertyNoSave(GAP_COLOUR, gapColour.getBackground());
+    Cache.setColourPropertyNoSave(HIDDEN_COLOUR, hiddenColour.getBackground());
     Cache.setPropertyNoSave(USE_LEGACY_GAP,
             Boolean.toString(useLegacyGap.isSelected()));
     Cache.setPropertyNoSave(SHOW_OV_HIDDEN_AT_START,
@@ -807,8 +807,11 @@ public class Preferences extends GPreferences
             Boolean.toString(useRnaView.isSelected()));
     Cache.setPropertyNoSave(STRUCT_FROM_PDB,
             Boolean.toString(structFromPdb.isSelected()));
-    Cache.setPropertyNoSave(STRUCTURE_DISPLAY,
-            structViewer.getSelectedItem().toString());
+    if (!Platform.isJS())
+    {
+      Cache.setPropertyNoSave(STRUCTURE_DISPLAY,
+              structViewer.getSelectedItem().toString());
+    }
     Cache.setOrRemove(CHIMERA_PATH, chimeraPath.getText());
     Cache.setPropertyNoSave("MAP_WITH_SIFTS",
             Boolean.toString(siftsMapping.isSelected()));
@@ -960,7 +963,7 @@ public class Preferences extends GPreferences
             BackupFilesPresetEntry.SAVEDCONFIG, savedBFPE.toString());
 
     Cache.saveProperties();
-    Desktop.instance.doConfigureStructurePrefs();
+    Desktop.getInstance().doConfigureStructurePrefs();
     try
     {
       frame.setClosed(true);
@@ -1107,7 +1110,7 @@ public class Preferences extends GPreferences
     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)
@@ -1159,7 +1162,7 @@ public class Preferences extends GPreferences
     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)
@@ -1332,7 +1335,7 @@ public class Preferences extends GPreferences
     } 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"),
@@ -1358,7 +1361,7 @@ public class Preferences extends GPreferences
       File f = new File(chimeraPath.getText());
       if (!f.canExecute())
       {
-        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                 MessageManager.getString("label.invalid_chimera_path"),
                 MessageManager.getString("label.invalid_name"),
                 JvOptionPane.ERROR_MESSAGE);
@@ -1397,7 +1400,7 @@ public class Preferences extends GPreferences
     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")),
               "", JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE,
index cb59452..77d83a8 100644 (file)
@@ -200,7 +200,7 @@ public class PromptUserConfig implements Runnable
     }
     try
     {
-      int reply = JvOptionPane.showConfirmDialog(Desktop.desktop, // component,
+      int reply = JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(), // component,
               dialogText, dialogTitle,
               (allowCancel) ? JvOptionPane.YES_NO_CANCEL_OPTION
                       : JvOptionPane.YES_NO_OPTION,
index 6ed3248..c7e2b8e 100755 (executable)
@@ -103,8 +103,8 @@ public class RedundancyPanel extends GSliderPanel implements Runnable
     frame.setContentPane(this);
     Desktop.addInternalFrame(frame,
             MessageManager
-                    .getString("label.redundancy_threshold_selection"),
-            true, FRAME_WIDTH, FRAME_HEIGHT, false, true);
+                    .getString("label.redundancy_threshold_selection"), Desktop.FRAME_MAKE_VISIBLE,
+            FRAME_WIDTH, FRAME_HEIGHT, Desktop.FRAME_NOT_RESIZABLE, Desktop.FRAME_ALLOW_ANY_SIZE);
     frame.addInternalFrameListener(new InternalFrameAdapter()
     {
       @Override
@@ -263,8 +263,7 @@ public class RedundancyPanel extends GSliderPanel implements Runnable
       ap.alignFrame.addHistoryItem(cut);
 
       PaintRefresher.Refresh(this, ap.av.getSequenceSetId(), true, true);
-      ap.av.firePropertyChange("alignment", null,
-              ap.av.getAlignment().getSequences());
+      ap.av.notifyAlignment();
     }
 
   }
@@ -289,8 +288,7 @@ public class RedundancyPanel extends GSliderPanel implements Runnable
     {
       command.undoCommand(af.getViewAlignments());
       ap.av.getHistoryList().remove(command);
-      ap.av.firePropertyChange("alignment", null,
-              ap.av.getAlignment().getSequences());
+      ap.av.notifyAlignment();
       af.updateEditMenuBar();
     }
 
index a6b4b49..fda1e03 100755 (executable)
@@ -180,9 +180,7 @@ public class ScalePanel extends JPanel
         {
           av.showColumn(hiddenRange[0]);
           reveal = null;
-          ap.updateLayout();
-          ap.paintAlignment(true, true);
-          av.sendSelection();
+          updatePanel();
         }
       });
       pop.add(item);
@@ -197,9 +195,7 @@ public class ScalePanel extends JPanel
           {
             av.showAllHiddenColumns();
             reveal = null;
-            ap.updateLayout();
-            ap.paintAlignment(true, true);
-            av.sendSelection();
+            updatePanel();
           }
         });
         pop.add(item);
@@ -221,10 +217,7 @@ public class ScalePanel extends JPanel
           {
             av.setSelectionGroup(null);
           }
-
-          ap.updateLayout();
-          ap.paintAlignment(true, true);
-          av.sendSelection();
+          updatePanel();
         }
       });
       pop.add(item);
@@ -232,6 +225,14 @@ public class ScalePanel extends JPanel
     return pop;
   }
 
+  protected void updatePanel()
+  {
+    ap.updateLayout();
+    ap.paintAlignment(true, true);
+    ap.updateScrollBarsFromRanges();
+    av.sendSelection();
+  }
+
   /**
    * Handles left mouse button press
    * 
index b27208a..f62507a 100755 (executable)
@@ -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;
 
@@ -75,7 +76,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
 
   private final SequenceRenderer seqRdr;
 
-  boolean fastPaint = false;
+  private boolean fastPaint = false;
 
   private boolean fastpainting = false;
 
@@ -94,8 +95,12 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
 
   private int wrappedVisibleWidths; // number of wrapped widths displayed
 
+  private int availWidth;
+
+  private int availHeight;
+
   // Don't do this! Graphics handles are supposed to be transient
-  //private Graphics2D gg;
+  // private Graphics2D gg;
 
   /**
    * Creates a new SeqCanvas object.
@@ -116,7 +121,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
 
   public SequenceRenderer getSequenceRenderer()
   {
-    return seqRdr; 
+    return seqRdr;
   }
 
   public FeatureRenderer getFeatureRenderer()
@@ -202,7 +207,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     int yPos = ypos + charHeight;
     int startX = startx;
     int endX = endx;
-    
+
     if (av.hasHiddenColumns())
     {
       HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
@@ -238,7 +243,6 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
         }
       }
 
-      
       /*
        * white fill the space for the scale
        */
@@ -343,7 +347,6 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
         }
       }
 
-
       // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
       // + horizontal + " " + vertical + " " + startRes + " " + endRes
       // + " " + startSeq + " " + endSeq);
@@ -352,9 +355,6 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       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);
@@ -374,19 +374,14 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
   @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;
     }
@@ -436,10 +431,11 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       // 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();
@@ -452,11 +448,11 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       }
 
       gg.setColor(Color.white);
-      gg.fillRect(0, 0, img.getWidth(), img.getHeight());
+      gg.fillRect(0, 0, availWidth, availHeight);
 
       if (av.getWrapAlignment())
       {
-        drawWrappedPanel(gg, width, height, ranges.getStartRes());
+        drawWrappedPanel(gg, availWidth, availHeight, ranges.getStartRes());
       }
       else
       {
@@ -474,7 +470,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       drawCursor(g, startRes, endRes, startSeq, endSeq);
     }
   }
-  
+
   /**
    * Draw an alignment panel for printing
    * 
@@ -494,8 +490,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
   {
     drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
 
-    drawSelectionGroup((Graphics2D) g1, startRes, endRes,
-            startSeq, endSeq);
+    drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
   }
 
   /**
@@ -519,37 +514,36 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     if (group != null)
     {
       drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
-                startRes);
+              startRes);
     }
   }
 
   /**
-   * Returns the visible width of the canvas in residues, after allowing for
-   * East or West scales (if shown)
+   * Using the current font, determine fields labelWidthEast and labelWidthWest,
+   * and return the number of residues that can fill the remaining width
    * 
-   * @param canvasWidth
+   * @param w
    *          the width in pixels (possibly including scales)
    * 
-   * @return
+   * @return the visible width in residues, after allowing for East or West
+   *         scales (if shown)
+   * 
    */
-  public int getWrappedCanvasWidth(int canvasWidth)
+  public int getWrappedCanvasWidth(int w)
   {
     int charWidth = av.getCharWidth();
 
     FontMetrics fm = getFontMetrics(av.getFont());
 
-    int labelWidth = 0;
-    
-    if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
-    {
-      labelWidth = getLabelWidth(fm);
-    }
+    int labelWidth = (av.getScaleRightWrapped() || av.getScaleLeftWrapped()
+            ? getLabelWidth(fm)
+            : 0);
 
     labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
 
     labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
 
-    return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
+    return (w - labelWidthEast - labelWidthWest) / charWidth;
   }
 
   /**
@@ -575,6 +569,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
     }
 
+    // quick int log10
     int length = 0;
     for (int i = maxWidth; i > 0; i /= 10)
     {
@@ -589,28 +584,25 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
    * 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),
@@ -623,9 +615,9 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     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++;
@@ -634,6 +626,16 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     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
@@ -644,12 +646,26 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
    * <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();
 
     /*
@@ -663,9 +679,8 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
      * 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
@@ -736,7 +751,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     int charWidth = av.getCharWidth();
     int xOffset = labelWidthWest
             + ((startColumn - ranges.getStartRes()) % viewportWidth)
-            * charWidth;
+                    * charWidth;
 
     g.translate(xOffset, 0);
 
@@ -803,7 +818,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       if (av.getScaleRightWrapped())
       {
         int x = labelWidthWest + viewportWidth * charWidth;
-        
+
         g.translate(x, 0);
         drawVerticalScale(g, startCol, endColumn, ypos, false);
         g.translate(-x, 0);
@@ -815,8 +830,9 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
        */
       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);
 
@@ -883,18 +899,21 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     }
   }
 
+  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();
@@ -902,6 +921,21 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
             / 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))
@@ -915,21 +949,24 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       }
 
       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
    */
@@ -1190,8 +1227,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
    *         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;
@@ -1262,7 +1298,6 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     }
   }
 
-
   /**
    * Draw a selection group over an unwrapped alignment
    * 
@@ -1285,7 +1320,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
           int startRes, int endRes, int startSeq, int endSeq, int offset)
   {
     int charWidth = av.getCharWidth();
-          
+
     if (!av.hasHiddenColumns())
     {
       drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
@@ -1308,8 +1343,8 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
         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;
@@ -1478,7 +1513,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       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
@@ -1494,7 +1529,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     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
@@ -1652,7 +1687,8 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       {
         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();
@@ -1670,92 +1706,161 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
   public void propertyChange(PropertyChangeEvent evt)
   {
     String eventName = evt.getPropertyName();
-    // System.err.println(">>SeqCanvas propertyChange " + eventName);
-    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;
-    }
 
-    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
-      {
-        scrollX = ((int[]) evt.getNewValue())[0]
-                - ((int[]) evt.getOldValue())[0];
-      }
-      ViewportRanges vpRanges = av.getRanges();
+    // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
+    // logic, emphasizing no check for ENDRES or ENDSEQ
 
-      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))
+
+    // Make sure we're not trying to draw a panel
+    // larger than the visible window
+    int scrollX = 0;
+    int scrollY = 0;
+    switch (eventName)
     {
-      // scroll
+    case SequenceGroup.SEQ_GROUP_CHANGED:
+      fastPaint = true;
+      repaint();
+      return;
+    case ViewportRanges.MOVE_VIEWPORT:
+      fastPaint = false;
+      repaint();
+      return;
+    case ViewportRanges.STARTSEQ:
+      // meaning STARTOREND
+      // typically scroll, but possibly just the end changed
       fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
-    }
-    else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
-    {
-      if (av.getWrapAlignment())
-      {
-        fastPaintWrapped(scrollX);
-      }
-      else
+      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];
+
+      // System.out.println("SC dx dy " + scrollX + " " + scrollY);
+
+      if (scrollX != 0 && scrollY != 0)
       {
-        fastPaint(scrollX, 0);
+        // all sorts of problems in JavaScript if this is commented out.
+        repaint();
+        return;
+
       }
+      break;
+    default:
+      return;
     }
-    else if (eventName.equals(ViewportRanges.STARTSEQ))
+
+    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())
     {
-      // scroll
-      fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+      fastPaintWrapped(scrollX);
     }
-    else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
+    else
     {
-      if (av.getWrapAlignment())
-      {
-        fastPaintWrapped(scrollX);
-      }
+      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.
+    // 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);
+    // }
+    // }
   }
 
   /**
@@ -1792,10 +1897,10 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
 
     try
     {
-      
+
       Graphics gg = img.getGraphics();
-      
-      calculateWrappedGeometry(getWidth(), getHeight());
+
+      calculateWrappedGeometry();
 
       /*
        * relocate the regions of the alignment that are still visible
@@ -1810,8 +1915,8 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       if (scrollX < 0)
       {
         int startRes = ranges.getStartRes();
-        drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
-                - scrollX - 1, getHeight());
+        drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes,
+                startRes - scrollX - 1, getHeight());
       }
       else
       {
@@ -1824,7 +1929,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       drawWrappedDecorators(gg, ranges.getStartRes());
 
       gg.dispose();
-      
+
       repaint();
     } finally
     {
@@ -1849,7 +1954,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     }
 
     Graphics gg = img.getGraphics();
-    
+
     ViewportRanges ranges = av.getRanges();
     int viewportWidth = ranges.getViewportWidth();
     int charWidth = av.getCharWidth();
@@ -1860,7 +1965,8 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
      */
     int visibleWidths = wrappedVisibleWidths;
     int canvasHeight = getHeight();
-    boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
+    boolean lastWidthPartHeight = (wrappedVisibleWidths
+            * wrappedRepeatHeightPx) > canvasHeight;
 
     if (lastWidthPartHeight)
     {
@@ -1876,16 +1982,15 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       /*
        * 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);
-      
+
     }
 
     /*
@@ -1930,7 +2035,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
       gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
     }
     gg.dispose();
- }
+  }
 
   /**
    * Shifts the visible alignment by the specified number of columns - left if
@@ -2018,8 +2123,8 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
         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;
@@ -2029,7 +2134,6 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     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
@@ -2050,7 +2154,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
 
     boolean matchFound = false;
 
-    calculateWrappedGeometry(getWidth(), getHeight());
+    calculateWrappedGeometry();
     int wrappedWidth = av.getWrappedWidth();
     int wrappedHeight = wrappedRepeatHeightPx;
 
@@ -2063,8 +2167,8 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     }
 
     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())
@@ -2077,7 +2181,6 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
 
     int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
 
-    
     Graphics gg = img.getGraphics();
 
     for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
@@ -2121,8 +2224,8 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
                * 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
@@ -2149,7 +2252,7 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
         }
       }
     }
-  
+
     gg.dispose();
 
     return matchFound;
@@ -2165,4 +2268,13 @@ public class SeqCanvas extends JPanel implements ViewportListenerI
     return labelWidthWest;
   }
 
+  /**
+   * Clears the flag that allows a 'fast paint' on the next repaint, so
+   * requiring a full repaint
+   */
+  public void setNoFastPaint()
+  {
+    fastPaint = false;
+  }
+
 }
index b2149df..3de631f 100644 (file)
@@ -291,8 +291,7 @@ public class SeqPanel extends JPanel
     int alignmentHeight = av.getAlignment().getHeight();
     if (av.getWrapAlignment())
     {
-      seqCanvas.calculateWrappedGeometry(seqCanvas.getWidth(),
-              seqCanvas.getHeight());
+      seqCanvas.calculateWrappedGeometry();
 
       /*
        * yPos modulo height of repeating width
@@ -424,8 +423,7 @@ public class SeqPanel extends JPanel
       if (editCommand != null && editCommand.getSize() > 0)
       {
         ap.alignFrame.addHistoryItem(editCommand);
-        av.firePropertyChange("alignment", null,
-                av.getAlignment().getSequences());
+        ap.av.notifyAlignment();
       }
     } finally
     {
index 8b5d3b7..3d09f99 100755 (executable)
@@ -145,8 +145,8 @@ public class SequenceFetcher extends JPanel implements Runnable
 
     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()
@@ -868,7 +868,7 @@ public class SequenceFetcher extends JPanel implements Runnable
       @Override
       public void run()
       {
-        JvOptionPane.showInternalMessageDialog(Desktop.desktop, error,
+        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(), error,
                 MessageManager.getString("label.error_retrieving_data"),
                 JvOptionPane.WARNING_MESSAGE);
       }
index 46b47a2..7effb0f 100755 (executable)
@@ -231,8 +231,8 @@ public class SliderPanel extends GSliderPanel
     if (!conservationSlider.isVisible())
     {
       Desktop.addInternalFrame(conservationSlider,
-              conservationSlider.getTitle(), true, FRAME_WIDTH,
-              FRAME_HEIGHT, false, true);
+              conservationSlider.getTitle(), Desktop.FRAME_MAKE_VISIBLE, FRAME_WIDTH,
+              FRAME_HEIGHT, Desktop.FRAME_NOT_RESIZABLE, Desktop.FRAME_ALLOW_ANY_SIZE);
       conservationSlider.addInternalFrameListener(new InternalFrameAdapter()
       {
         @Override
@@ -307,8 +307,8 @@ public class SliderPanel extends GSliderPanel
 
     if (!PIDSlider.isVisible())
     {
-      Desktop.addInternalFrame(PIDSlider, PIDSlider.getTitle(), true,
-              FRAME_WIDTH, FRAME_HEIGHT, false, true);
+      Desktop.addInternalFrame(PIDSlider, PIDSlider.getTitle(), Desktop.FRAME_MAKE_VISIBLE,
+              FRAME_WIDTH, FRAME_HEIGHT, Desktop.FRAME_NOT_RESIZABLE, Desktop.FRAME_ALLOW_ANY_SIZE);
       PIDSlider.setLayer(JLayeredPane.PALETTE_LAYER);
       PIDSlider.addInternalFrameListener(new InternalFrameAdapter()
       {
index f4b275d..0edede4 100755 (executable)
@@ -160,7 +160,7 @@ public class SplashScreen extends JPanel
             System.err.println("Error when loading images!");
           }
         } while (!mt.checkAll());
-        Desktop.instance.setIconImage(logo);
+        Desktop.getInstance().setIconImage(logo);
       }
     } catch (Exception ex)
     {
@@ -191,7 +191,7 @@ public class SplashScreen extends JPanel
     }
     add(splashText, BorderLayout.CENTER);
     splashText.addMouseListener(closer);
-    Desktop.desktop.add(iframe);
+    Desktop.getDesktopPane().add(iframe);
     refreshText();
   }
 
@@ -200,7 +200,7 @@ public class SplashScreen extends JPanel
    */
   protected boolean refreshText()
   {
-    String newtext = Desktop.instance.getAboutMessage();
+    String newtext = Desktop.getInstance().getAboutMessage();
     // System.err.println("Text found: \n"+newtext+"\nEnd of newtext.");
     if (oldTextLength != newtext.length())
     {
@@ -239,8 +239,8 @@ public class SplashScreen extends JPanel
       splashText.setSize(new Dimension(750, 375));
       add(splashText, BorderLayout.CENTER);
       revalidate();
-      iframe.setBounds((Desktop.instance.getWidth() - 750) / 2,
-              (Desktop.instance.getHeight() - 375) / 2, 750,
+      iframe.setBounds((Desktop.getInstance().getWidth() - 750) / 2,
+              (Desktop.getInstance().getHeight() - 375) / 2, 750,
               splashText.getHeight() + iconimg.getHeight());
       iframe.validate();
       iframe.setVisible(true);
@@ -286,7 +286,7 @@ public class SplashScreen extends JPanel
     }
 
     closeSplash();
-    Desktop.instance.startDialogQueue();
+    Desktop.getInstance().startDialogQueue();
   }
 
   /**
index f57279e..953755f 100644 (file)
@@ -152,7 +152,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
     // 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();
@@ -170,7 +170,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
     // 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());
   }
@@ -571,7 +571,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
     adjustLayout();
 
     final StructureSelectionManager ssm = StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance);
+            .getStructureSelectionManager(Desktop.getInstance());
     ssm.addCommandListener(newTopPanel.av);
     ssm.addCommandListener(newBottomPanel.av);
   }
@@ -698,7 +698,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
    */
   protected void expandViews_actionPerformed()
   {
-    Desktop.instance.explodeViews(this);
+    Desktop.getInstance().explodeViews(this);
   }
 
   /**
@@ -707,7 +707,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
    */
   protected void gatherViews_actionPerformed()
   {
-    Desktop.instance.gatherViews(this);
+    Desktop.getInstance().gatherViews(this);
   }
 
   /**
index 4a8a7d8..c30d8c7 100644 (file)
@@ -145,7 +145,7 @@ public class StructureChooser extends GStructureChooser
    */
   private void discoverStructureViews()
   {
-    if (Desktop.instance != null)
+    if (Desktop.getInstance() != null)
     {
       targetView.removeAllItems();
       if (lastTargetedView != null && !lastTargetedView.isVisible())
@@ -153,7 +153,7 @@ public class StructureChooser extends GStructureChooser
         lastTargetedView = null;
       }
       int linkedViewsAt = 0;
-      for (StructureViewerBase view : Desktop.instance
+      for (StructureViewerBase view : Desktop.getInstance()
               .getStructureViewers(null, null))
       {
         StructureViewer viewHandler = (lastTargetedView != null
@@ -886,11 +886,9 @@ public class StructureChooser extends GStructureChooser
   public void showStructures(boolean waitUntilFinished)
   {
 
-    final StructureSelectionManager ssm = ap.getStructureSelectionManager();
-
     final int preferredHeight = pnl_filter.getHeight();
 
-    final StructureViewer theViewer = getTargetedStructureViewer(ssm);
+    final StructureViewer theViewer = getTargetedStructureViewer();
     boolean superimpose = chk_superpose.isSelected();
 
     Runnable viewStruc = new Runnable()
@@ -929,9 +927,7 @@ public class StructureChooser extends GStructureChooser
 
             if (pdbEntry == null)
             {
-              pdbEntry = new PDBEntry();
-              pdbEntry.setId(pdbIdStr);
-              pdbEntry.setType(PDBEntry.Type.PDB);
+              pdbEntry = new PDBEntry(pdbIdStr, null, "pdb");
               selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
             }
             pdbEntriesToView[count++] = pdbEntry;
@@ -1005,10 +1001,8 @@ public class StructureChooser extends GStructureChooser
           {
             selectedSequence = userSelectedSeq;
           }
-          PDBEntry fileEntry = new AssociatePdbFileWithSeq()
-                  .associatePdbWithSeq(selectedPdbFileName,
-                          DataSourceType.FILE, selectedSequence, true,
-                          Desktop.instance);
+          PDBEntry fileEntry = AssociatePdbFileWithSeq.associatePdbWithSeq(selectedPdbFileName,
+                          DataSourceType.FILE, selectedSequence, true);
           sViewer = StructureViewer.launchStructureViewer(ap, new PDBEntry[] { fileEntry },
                   new SequenceI[]
                   { selectedSequence }, superimpose, theViewer,
@@ -1066,11 +1060,9 @@ public class StructureChooser extends GStructureChooser
    * @param ssm
    * @return
    */
-  StructureViewer getTargetedStructureViewer(StructureSelectionManager ssm)
+  StructureViewer getTargetedStructureViewer()
   {
-    Object sv = targetView.getSelectedItem();
-
-    return sv == null ? new StructureViewer(ssm) : (StructureViewer) sv;
+    return (StructureViewer) targetView.getSelectedItem();
   }
 
   /**
index e0c33e5..e7a30f9 100644 (file)
@@ -433,6 +433,8 @@ public class StructureViewer
           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(
index 418a84d..af48093 100644 (file)
@@ -407,7 +407,7 @@ public abstract class StructureViewerBase extends GStructureViewer
    */
   protected List<StructureViewerBase> getViewersFor(AlignmentPanel alp)
   {
-    return Desktop.instance.getStructureViewers(alp, this.getClass());
+    return Desktop.getInstance().getStructureViewers(alp, this.getClass());
   }
 
   @Override
index 29ba52b..e59dfcc 100755 (executable)
@@ -761,7 +761,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
      */
     if (e.isPopupTrigger())
     {
-      chooseSubtreeColour();
+      if (highlightNode != null) {
+        chooseSubtreeColour();
+      }
       e.consume(); // prevent mouseClicked happening
     }
   }
index 37b4dac..d242892 100755 (executable)
@@ -174,11 +174,12 @@ public class TreePanel extends GTreePanel
   {
     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");
@@ -191,12 +192,14 @@ public class TreePanel extends GTreePanel
           {
             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;
         }
       }
     };
index 4846049..1139ae0 100755 (executable)
@@ -149,8 +149,8 @@ public class UserDefinedColours extends GUserDefinedColours
     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);
   }
 
   /**
@@ -448,7 +448,7 @@ public class UserDefinedColours extends GUserDefinedColours
   {
     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"),
@@ -503,7 +503,7 @@ public class UserDefinedColours extends GUserDefinedColours
         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]);
 
@@ -557,7 +557,7 @@ public class UserDefinedColours extends GUserDefinedColours
   {
     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"),
@@ -741,7 +741,7 @@ public class UserDefinedColours extends GUserDefinedColours
     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"),
@@ -756,7 +756,7 @@ public class UserDefinedColours extends GUserDefinedColours
        * @j2sIgnore
        */
       {
-        int reply = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
+        int reply = JvOptionPane.showInternalConfirmDialog(Desktop.getDesktopPane(),
                 MessageManager.formatMessage(
                         "label.colour_scheme_exists_overwrite", new Object[]
                         { name, name }),
index ef86756..e12586a 100644 (file)
@@ -141,7 +141,7 @@ public class UserQuestionnaireCheck implements Runnable
                 + qid + "&rid=" + rid;
         jalview.bin.Cache.log
                 .info("Prompting user for questionnaire at " + qurl);
-        int reply = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
+        int reply = JvOptionPane.showInternalConfirmDialog(Desktop.getDesktopPane(),
                 MessageManager.getString("label.jalview_new_questionnaire"),
                 MessageManager.getString("label.jalview_user_survey"),
                 JvOptionPane.YES_NO_OPTION, JvOptionPane.QUESTION_MESSAGE);
index 0848a4d..2ee3e06 100644 (file)
@@ -173,7 +173,7 @@ public class VamsasApplication implements SelectionSource, VamsasSource
           }
         } catch (InvalidSessionDocumentException e)
         {
-          JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+          JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
 
                   MessageManager.getString(
                           "label.vamsas_doc_couldnt_be_opened_as_new_session"),
@@ -455,7 +455,7 @@ public class VamsasApplication implements SelectionSource, VamsasSource
     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)
     {
@@ -660,7 +660,7 @@ public class VamsasApplication implements SelectionSource, VamsasSource
                   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,
@@ -669,7 +669,7 @@ public class VamsasApplication implements SelectionSource, VamsasSource
                   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.");
                   }
@@ -689,7 +689,7 @@ public class VamsasApplication implements SelectionSource, VamsasSource
   public void disableGui(boolean b)
   {
     // JAL-3311 TODO: remove this class!
-    // Desktop.instance.setVamsasUpdate(b);
+    // Desktop.getInstance().setVamsasUpdate(b);
   }
 
   Hashtable _backup_vobj2jv;
@@ -764,7 +764,7 @@ public class VamsasApplication implements SelectionSource, VamsasSource
       {
         final IPickManager pm = vclient.getPickManager();
         final StructureSelectionManager ssm = StructureSelectionManager
-                .getStructureSelectionManager(Desktop.instance);
+                .getStructureSelectionManager(Desktop.getInstance());
         final VamsasApplication me = this;
         pm.registerMessageHandler(new IMessageHandler()
         {
index 25ade21..ab9444e 100644 (file)
@@ -259,6 +259,7 @@ public class WebserviceInfo extends GWebserviceInfo
   public WebserviceInfo(String title, String info, int width, int height,
           boolean makeVisible)
   {
+    // no references
     init(title, info, width, height, makeVisible);
   }
 
@@ -322,7 +323,7 @@ public class WebserviceInfo extends GWebserviceInfo
   {
     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);
@@ -746,7 +747,7 @@ public class WebserviceInfo extends GWebserviceInfo
       @Override
       public void run()
       {
-        JvOptionPane.showInternalMessageDialog(Desktop.desktop, message,
+        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(), message,
                 title, JvOptionPane.WARNING_MESSAGE);
 
       }
index 976e551..45822a3 100644 (file)
@@ -215,12 +215,12 @@ public class WsJobParameters extends JPanel implements ItemListener,
   public boolean showRunDialog()
   {
 
-    frame = new JDialog(Desktop.instance, true);
+    frame = new JDialog(Desktop.getInstance(), true);
 
     frame.setTitle(MessageManager.formatMessage("label.edit_params_for",
             new String[]
             { service.getActionText() }));
-    Rectangle deskr = Desktop.instance.getBounds();
+    Rectangle deskr = Desktop.getInstance().getBounds();
     Dimension pref = this.getPreferredSize();
     frame.setBounds(
             new Rectangle((int) (deskr.getCenterX() - pref.width / 2),
@@ -436,8 +436,8 @@ public class WsJobParameters extends JPanel implements ItemListener,
     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 ? 540
+            : Desktop.getInstance().getHeight();
     setPreferredSize(new Dimension(540, windowHeight));
     add(dialogpanel, BorderLayout.SOUTH);
     validate();
@@ -963,13 +963,13 @@ public class WsJobParameters extends JPanel implements ItemListener,
   public static void main(String[] args)
   {
     jalview.ws.jws2.Jws2Discoverer disc = jalview.ws.jws2.Jws2Discoverer
-            .getDiscoverer();
+            .getInstance();
     int p = 0;
     if (args.length > 0)
     {
       Vector<String> services = new Vector<>();
       services.addElement(args[p++]);
-      Jws2Discoverer.getDiscoverer().setServiceUrls(services);
+      Jws2Discoverer.getInstance().setServiceUrls(services);
     }
     try
     {
index bb5d996..0f315eb 100644 (file)
@@ -203,7 +203,7 @@ public class WsParamSetManager implements ParamManager
       chooser.setDialogTitle(MessageManager
               .getString("label.choose_filename_for_param_file"));
       chooser.setToolTipText(MessageManager.getString("action.save"));
-      int value = chooser.showSaveDialog(Desktop.instance);
+      int value = chooser.showSaveDialog(Desktop.getInstance());
       if (value == JalviewFileChooser.APPROVE_OPTION)
       {
         outfile = chooser.getSelectedFile();
@@ -311,7 +311,7 @@ public class WsParamSetManager implements ParamManager
       File pfile = new File(filename);
       if (pfile.exists() && pfile.canWrite())
       {
-        if (JvOptionPane.showConfirmDialog(Desktop.instance,
+        if (JvOptionPane.showConfirmDialog(Desktop.getInstance(),
                 "Delete the preset's file, too ?", "Delete User Preset ?",
                 JvOptionPane.OK_CANCEL_OPTION) == JvOptionPane.OK_OPTION)
         {
index 5186a26..b97a25e 100644 (file)
@@ -65,7 +65,7 @@ public class WsPreferences extends GWsPreferences
   private void initFromPreferences()
   {
 
-    wsUrls = Jws2Discoverer.getDiscoverer().getServiceUrls();
+    wsUrls = Jws2Discoverer.getInstance().getServiceUrls();
     if (!wsUrls.isEmpty())
     {
       oldUrls = new Vector<String>(wsUrls);
@@ -122,7 +122,7 @@ public class WsPreferences extends GWsPreferences
     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;
     }
@@ -241,7 +241,7 @@ public class WsPreferences extends GWsPreferences
 
   private void updateServiceList()
   {
-    Jws2Discoverer.getDiscoverer().setServiceUrls(wsUrls);
+    Jws2Discoverer.getInstance().setServiceUrls(wsUrls);
   }
 
   private void updateRsbsServiceList()
@@ -454,7 +454,7 @@ public class WsPreferences extends GWsPreferences
     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
@@ -472,13 +472,13 @@ public class WsPreferences extends GWsPreferences
       } 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);
@@ -491,7 +491,7 @@ public class WsPreferences extends GWsPreferences
         }
         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,
@@ -502,7 +502,7 @@ public class WsPreferences extends GWsPreferences
           }
           else
           {
-            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+            JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                     MessageManager.getString(
                             "warn.server_didnt_pass_validation"));
           }
@@ -595,7 +595,7 @@ public class WsPreferences extends GWsPreferences
           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();
 
@@ -616,15 +616,15 @@ public class WsPreferences extends GWsPreferences
         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();
@@ -646,8 +646,8 @@ public class WsPreferences extends GWsPreferences
   @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++;
index a18d38d..7021fae 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.httpserver;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.rest.RestHandler;
 
 import java.net.BindException;
@@ -49,39 +51,8 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
  * @author gmcarstairs
  * @see http://eclipse.org/jetty/documentation/current/embedding-jetty.html
  */
-public class HttpServer
+public class HttpServer implements ApplicationSingletonI
 {
-  /*
-   * 'context root' - actually just prefixed to the path for each handler for
-   * now - see registerHandler
-   */
-  private static final String JALVIEW_PATH = "jalview";
-
-  /*
-   * Singleton instance of this server
-   */
-  private static HttpServer instance;
-
-  /*
-   * The Http server
-   */
-  private Server server;
-
-  /*
-   * Registered handlers for context paths
-   */
-  private HandlerCollection contextHandlers;
-
-  /*
-   * Lookup of ContextHandler by its wrapped handler
-   */
-  Map<Handler, ContextHandler> myHandlers = new HashMap<Handler, ContextHandler>();
-
-  /*
-   * The context root for the server
-   */
-  private URI contextRoot;
-
   /**
    * Returns the singleton instance of this class.
    * 
@@ -92,14 +63,10 @@ public class HttpServer
   {
     synchronized (HttpServer.class)
     {
-      if (instance == null)
-      {
-        instance = new HttpServer();
-      }
-      return instance;
+      return (HttpServer) ApplicationSingletonProvider.getInstance(HttpServer.class);
     }
   }
-
+  
   /**
    * Private constructor to enforce use of singleton
    * 
@@ -116,6 +83,34 @@ public class HttpServer
     registerHandler(RestHandler.getInstance());
   }
 
+
+  /*
+   * 'context root' - actually just prefixed to the path for each handler for
+   * now - see registerHandler
+   */
+  private static final String JALVIEW_PATH = "jalview";
+
+  /*
+   * The Http server
+   */
+  private Server server;
+
+  /*
+   * Registered handlers for context paths
+   */
+  private HandlerCollection contextHandlers;
+
+  /*
+   * Lookup of ContextHandler by its wrapped handler
+   */
+  Map<Handler, ContextHandler> myHandlers = new HashMap<Handler, ContextHandler>();
+
+  /*
+   * The context root for the server
+   */
+  private URI contextRoot;
+
+
   /**
    * Start the http server
    * 
index 4916bb3..f01bfdc 100755 (executable)
@@ -158,11 +158,7 @@ public class AppletFormatAdapter
   {
 
     this.selectedFile = selectedFile;
-    if (selectedFile != null)
-    {
-      this.inFile = selectedFile.getPath();
-    }
-    this.inFile = file;
+    inFile = (selectedFile == null ? file : selectedFile.getPath());
     try
     {
       if (fileFormat.isStructureFile())
diff --git a/src/jalview/io/BSMLFile.java b/src/jalview/io/BSMLFile.java
new file mode 100644 (file)
index 0000000..69e29f8
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.io;
+
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.util.MessageManager;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax;
+import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed;
+import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied;
+
+/**
+ * Preliminary reader for Bioinformatics Sequence Markup Language
+ * http://www.bsml.org
+ * 
+ * @author hansonr
+ *
+ */
+public class BSMLFile extends AlignFile
+{
+
+  public BSMLFile()
+  {
+    super();
+
+  }
+
+  public BSMLFile(String inFile, DataSourceType type) throws IOException
+  {
+    super(inFile, type);
+
+  }
+
+  public BSMLFile(FileParse source) throws IOException
+  {
+    super(source);
+
+  }
+
+  public BufferedReader CreateReader() throws FileNotFoundException
+  {
+    FileReader fr = null;
+    fr = new FileReader(inFile);
+
+    BufferedReader r = new BufferedReader(fr);
+    return r;
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see jalview.io.AlignFile#parse()
+   */
+  @Override
+  public void parse() throws IOException
+  {
+    try
+    {
+      _parse();
+    } catch (ExceptionPermissionDenied pdx)
+    {
+      errormessage = MessageManager.formatMessage(
+              "exception.BSML_couldnt_access_datasource", new String[]
+              { pdx.getMessage() });
+      throw new IOException(pdx);
+    } catch (ExceptionLoadingFailed lf)
+    {
+      errormessage = MessageManager.formatMessage(
+              "exception.BSML_couldnt_process_data", new String[]
+              { lf.getMessage() });
+      throw new IOException(lf);
+    } catch (ExceptionFileFormatOrSyntax iff)
+    {
+      errormessage = MessageManager
+              .formatMessage("exception.BSML_invalid_file", new String[]
+              { iff.getMessage() });
+      throw new IOException(iff);
+    } catch (Exception x)
+    {
+      error = true;
+      errormessage = MessageManager.formatMessage(
+              "exception.BSML_problem_parsing_data", new String[]
+              { x.getMessage() });
+      throw new IOException(errormessage, x);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  public void _parse()
+          throws ExceptionPermissionDenied, ExceptionLoadingFailed,
+          ExceptionFileFormatOrSyntax, ParserConfigurationException,
+          SAXException, IOException
+  {
+
+    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+
+    dbf.setIgnoringElementContentWhitespace(true);
+    dbf.setIgnoringComments(true);
+    dbf.setValidating(true);
+    dbf.setCoalescing(true);
+    dbf.setNamespaceAware(true);
+      dbf.setFeature("http://xml.org/sax/features/namespaces", false);
+      dbf.setFeature("http://xml.org/sax/features/validation", false);
+      dbf.setFeature(
+              "http://apache.org/xml/features/nonvalidating/load-dtd-grammar",
+              false);
+      dbf.setFeature(
+              "http://apache.org/xml/features/nonvalidating/load-external-dtd",
+              false);
+
+      DocumentBuilder db = dbf.newDocumentBuilder();
+
+      Map<String, SequenceI> htSeq = new Hashtable<>();
+      InputSource is = new InputSource(getReader());
+      Document d = db.parse(is);
+      NodeList sequences = d.getElementsByTagName("Sequence-data");
+      int n = sequences.getLength();
+      SequenceI[] sqs = new SequenceI[n];
+      for (int i = 0; i < n; i++)
+      {
+        Element e = (Element) sequences.item(i);
+        String s = e.getTextContent();
+        String id = e.getAttribute("seq-name");
+        SequenceI seq = sqs[i] = new Sequence(id, s, 1, s.length());
+        htSeq.put(id, seq);
+        // ?? sqs[i].setEnd(sqs[i].findPosition(sqs[i].getLength()));
+      }
+
+      sequences = d.getElementsByTagName("Sequence");
+      n = sequences.getLength();
+      for (int i = 0; i < n; i++)
+      {
+        Element e = (Element) sequences.item(i);
+        String mol = e.getAttribute("molecule"); // dna or rna
+        if (!"dna".equals(mol))
+        {
+          System.err.println("BSML molecule=rna not implemented");
+          continue;
+        }
+        String title = e.getAttribute("title");
+        SequenceI seq = htSeq.get(title);
+        if (seq == null)
+        {
+          continue;
+        }
+      NodeList features = e.getElementsByTagName("Feature");
+        int featureCount = features.getLength();
+        for (int f = 0; f < featureCount; f++)
+        {
+          Element feature = (Element) features.item(f);
+          // <Feature class="GENE" title="CPXV-GER_1980_EP4-211">
+          // <Interval-loc complement="0" endpos="217104" startpos="216643"/>
+          // <Resource id="GENE-ID:119705"/>
+          // </Feature>
+        Element iloc = (Element) feature
+                .getElementsByTagName("Interval-loc").item(0);
+          String complement = iloc.getAttribute("complement");
+          if (!"0".equals(complement))
+          {
+            // Jalview cannot handle complement genes (running backward on the
+            // complementary strand);
+            continue;
+          }
+          String fclass = feature.getAttribute("class");
+          if (!"GENE".equals(fclass))
+          {
+            // just processing GENE features for now;
+            continue;
+          }
+          String ftitle = feature.getAttribute("title");
+        int start = Integer.parseInt(iloc.getAttribute("startpos"));
+        int end = Integer.parseInt(iloc.getAttribute("endpos"));
+        SequenceFeature sf = new SequenceFeature("GENE", ftitle, start, end,
+                null);
+        seq.addSequenceFeature(sf);
+        }
+        setSeqs(sqs);
+      }
+
+  }
+
+  @Override
+  public String print(SequenceI[] s, boolean jvSuffix)
+  {
+    return "not yet implemented";
+  }
+
+}
index 0d5f92b..9537442 100644 (file)
@@ -538,7 +538,7 @@ public class BackupFiles
           MessageManager.getString("label.delete"),
           MessageManager.getString("label.rename") };
 
-      confirmButton = JvOptionPane.showOptionDialog(Desktop.desktop,
+      confirmButton = JvOptionPane.showOptionDialog(Desktop.getDesktopPane(),
               messageSB.toString(),
               MessageManager.getString("label.backupfiles_confirm_delete"),
               JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE,
@@ -558,7 +558,7 @@ public class BackupFiles
           MessageManager.getString("label.delete"),
           MessageManager.getString("label.keep") };
 
-      confirmButton = JvOptionPane.showOptionDialog(Desktop.desktop,
+      confirmButton = JvOptionPane.showOptionDialog(Desktop.getDesktopPane(),
               messageSB.toString(),
               MessageManager.getString("label.backupfiles_confirm_delete"),
               JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE,
@@ -593,7 +593,7 @@ public class BackupFiles
                       Long.toString(df.length()) }));
         }
 
-        int confirmButton = JvOptionPane.showConfirmDialog(Desktop.desktop,
+        int confirmButton = JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
                 messageSB.toString(),
                 MessageManager
                         .getString("label.backupfiles_confirm_delete"),
@@ -682,7 +682,7 @@ public class BackupFiles
                 "label.backupfiles_confirm_save_new_saved_file_not_ok"));
       }
 
-      int confirmButton = JvOptionPane.showConfirmDialog(Desktop.desktop,
+      int confirmButton = JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
               messageSB.toString(),
               MessageManager
                       .getString("label.backupfiles_confirm_save_file"),
index 9db3df2..5573e0d 100644 (file)
@@ -25,6 +25,7 @@ import jalview.gui.OOMWarning;
 import jalview.json.binding.biojs.BioJSReleasePojo;
 import jalview.json.binding.biojs.BioJSRepositoryPojo;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedReader;
@@ -44,8 +45,7 @@ public class BioJsHTMLOutput extends HTMLOutput
 
   private static TreeMap<String, File> bioJsMSAVersions;
 
-  public static final String DEFAULT_DIR = System.getProperty("user.home")
-          + File.separatorChar + ".biojs_templates" + File.separatorChar;
+  public static final String DEFAULT_DIR = Platform.getUserPath(".biojs_templates/");
 
   public static final String BJS_TEMPLATES_LOCAL_DIRECTORY = jalview.bin.Cache
           .getDefault("biojs_template_directory", DEFAULT_DIR);
index aadcdb9..fca29bd 100644 (file)
@@ -27,6 +27,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
@@ -36,23 +39,11 @@ import java.util.Set;
  * @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);
   }
 
   /**
@@ -63,6 +54,17 @@ public class FileFormats
     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
index ac55911..7eda110 100755 (executable)
@@ -275,9 +275,9 @@ public class FileLoader implements Runnable
     Runtime rt = Runtime.getRuntime();
     try
     {
-      if (Desktop.instance != null)
+      if (Desktop.getInstance() != null)
       {
-        Desktop.instance.startLoading(file);
+        Desktop.getInstance().startLoading(file);
       }
       if (format == null)
       {
@@ -299,12 +299,12 @@ public class FileLoader implements Runnable
 
       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(),
@@ -315,7 +315,7 @@ public class FileLoader implements Runnable
       }
       // 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
@@ -408,7 +408,7 @@ public class FileLoader implements Runnable
                 // register PDB entries with desktop's structure selection
                 // manager
                 StructureSelectionManager
-                        .getStructureSelectionManager(Desktop.instance)
+                        .getStructureSelectionManager(Desktop.getInstance())
                         .registerPDBEntry(pdbe);
               }
             }
@@ -501,23 +501,23 @@ public class FileLoader implements Runnable
         }
         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"),
@@ -545,7 +545,7 @@ public class FileLoader implements Runnable
           @Override
           public void run()
           {
-            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+            JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                     MessageManager.formatMessage(
                             "label.problems_opening_file", new String[]
                             { file }),
@@ -567,7 +567,7 @@ public class FileLoader implements Runnable
           @Override
           public void run()
           {
-            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+            JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                     MessageManager.formatMessage(
                             "warn.out_of_memory_loading_file", new String[]
                             { file }),
@@ -589,7 +589,7 @@ public class FileLoader implements Runnable
     // memory
     // after
     // load
-    if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
+    if (Desktop.getDesktopPane() != null && Desktop.getDesktopPane().isShowMemoryUsage())
     {
       if (alignFrame != null)
       {
@@ -611,9 +611,9 @@ public class FileLoader implements Runnable
       }
     }
     // remove the visual delay indicator
-    if (Desktop.instance != null)
+    if (Desktop.getInstance() != null)
     {
-      Desktop.instance.stopLoading();
+      Desktop.getInstance().stopLoading();
     }
 
   }
index 8e4e783..4d0bec7 100644 (file)
@@ -237,7 +237,7 @@ public class SequenceAnnotationReport
       {
         if (sb0.length() > 6)
         {
-          sb.append("<br/>");
+          sb.append("<br>");
         }
         sb.append(feature.getType()).append(" ").append(begin).append(":")
                 .append(end);
@@ -247,7 +247,7 @@ public class SequenceAnnotationReport
 
     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;
@@ -276,7 +276,9 @@ public class SequenceAnnotationReport
         int linkindex = description.toLowerCase().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;
@@ -395,7 +397,7 @@ public class SequenceAnnotationReport
           {
             for (List<String> urllink : createLinksFrom(null, urlstring))
             {
-              sb.append("<br/> <a href=\""
+              sb.append("<br> <a href=\""
                       + urllink.get(3)
                       + "\" target=\""
                       + urllink.get(0)
@@ -404,7 +406,7 @@ public class SequenceAnnotationReport
                               .equals(urllink.get(1).toLowerCase()) ? urllink
                               .get(0) : (urllink.get(0) + ":" + urllink
                                               .get(1)))
-                      + "</a><br/>");
+                      + "</a><br>");
             }
           } catch (Exception x)
           {
@@ -561,7 +563,7 @@ public class SequenceAnnotationReport
       countForSource++;
       if (countForSource == 1 || !summary)
       {
-        sb.append("<br/>");
+        sb.append("<br>");
       }
       if (countForSource <= MAX_REFS_PER_SOURCE || !summary)
       {
@@ -587,11 +589,11 @@ public class SequenceAnnotationReport
     }
     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(")");
     }
index 387dbfa..c60fd88 100644 (file)
@@ -720,9 +720,9 @@ public class VamsasAppDatastore
       // /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++)
@@ -1479,7 +1479,7 @@ public class VamsasAppDatastore
           if (mappings != null)
           {
             jalview.structure.StructureSelectionManager
-                    .getStructureSelectionManager(Desktop.instance)
+                    .getStructureSelectionManager(Desktop.getInstance())
                     .registerMappings(mappings);
           }
         }
index 63b78b2..e24c2f3 100755 (executable)
@@ -150,7 +150,7 @@ public class WSWUBlastClient
   {
     // This must be outside the run() body as java 1.5
     // will not return any value from the OptionPane to the expired thread.
-    int reply = JvOptionPane.showConfirmDialog(Desktop.desktop,
+    int reply = JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
             "Automatically update suggested ids?",
             "Auto replace sequence ids", JvOptionPane.YES_NO_OPTION);
 
index eaf6ecd..fdcad08 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.io.cache;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.bin.Cache;
 
 import java.util.Hashtable;
@@ -31,22 +33,27 @@ import java.util.LinkedHashSet;
  * @author tcnofoegbu
  *
  */
-public class AppCache
+public class AppCache implements ApplicationSingletonI
 {
+
+  public static AppCache getInstance()
+  {
+    return (AppCache) ApplicationSingletonProvider.getInstance(AppCache.class);
+  }
+
+  private AppCache()
+  {
+    cacheItems = new Hashtable<String, LinkedHashSet<String>>();
+  }
+
   public static final String DEFAULT_LIMIT = "99";
 
   public static final String CACHE_DELIMITER = ";";
 
-  private static AppCache instance = null;
-
   private static final String DEFAULT_LIMIT_KEY = ".DEFAULT_LIMIT";
 
   private Hashtable<String, LinkedHashSet<String>> cacheItems;
 
-  private AppCache()
-  {
-    cacheItems = new Hashtable<String, LinkedHashSet<String>>();
-  }
 
   /**
    * Method to obtain all the cache items for a given cache key
@@ -66,20 +73,6 @@ public class AppCache
   }
 
   /**
-   * Returns a singleton instance of AppCache
-   * 
-   * @return
-   */
-  public static AppCache getInstance()
-  {
-    if (instance == null)
-    {
-      instance = new AppCache();
-    }
-    return instance;
-  }
-
-  /**
    * Method for persisting cache items for a given cache key
    * 
    * @param cacheKey
index 1ef8848..e422ed4 100644 (file)
@@ -91,7 +91,7 @@ public class Gff3Helper extends GffHelperBase
       String atts = gff[ATTRIBUTES_COL];
       Map<String, List<String>> attributes = parseNameValuePairs(atts);
 
-      SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+      SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology();
       if (so.isA(soTerm, SequenceOntologyI.PROTEIN_MATCH))
       {
         sf = processProteinMatch(attributes, seq, gff, align, newseqs,
@@ -385,7 +385,7 @@ public class Gff3Helper extends GffHelperBase
       desc = target.split(" ")[0];
     }
 
-    SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+    SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology();
     String type = sf.getType();
     if (so.isA(type, SequenceOntologyI.SEQUENCE_VARIANT))
     {
index 948cdd2..141b677 100644 (file)
@@ -108,7 +108,7 @@ public class InterProScanHelper extends Gff3Helper
    */
   public static boolean recognises(String[] columns)
   {
-    SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+    SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology();
     String type = columns[TYPE_COL];
     if (so.isA(type, SequenceOntologyI.PROTEIN_MATCH)
             || (".".equals(columns[SOURCE_COL])
index 90cae7a..08a2c6a 100644 (file)
@@ -20,6 +20,9 @@
  */
 package jalview.io.gff;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
+
 /**
  * A factory class that returns a model of the Sequence Ontology. By default a
  * hard-coded subset is used (for the applet, or testing), or setInstance() can
@@ -28,21 +31,53 @@ package jalview.io.gff;
  * @author gmcarstairs
  *
  */
-public class SequenceOntologyFactory
+public class SequenceOntologyFactory implements ApplicationSingletonI
 {
-  private static SequenceOntologyI instance;
+  /**
+   * Answers an instance of this class for the current application context. Note
+   * that this supports running two JS 'applets' on the same page, one with the
+   * full Sequence Ontology (USE_FULL_SO = true) and one with a hard-coded
+   * subset (USE_FULL_SO = false). If this is overkill, could change this method
+   * to just return a common static instance.
+   * 
+   * @return
+   */
+  private static synchronized SequenceOntologyFactory getInstance()
+  {
+    return (SequenceOntologyFactory) ApplicationSingletonProvider
+            .getInstance(SequenceOntologyFactory.class);
+  }
+
+  private SequenceOntologyFactory()
+  {
+    // private singleton
+  }
+
 
-  public static synchronized SequenceOntologyI getInstance()
+  /**
+   * Answers the configured model of the Sequence Ontology.
+   * 
+   * @return
+   */
+  public static synchronized SequenceOntologyI getSequenceOntology()
   {
-    if (instance == null)
-    {
-      instance = new SequenceOntologyLite();
-    }
-    return instance;
+    SequenceOntologyFactory f = getInstance();
+    return (f.sequenceOntology == null
+            ? f.sequenceOntology = new SequenceOntologyLite()
+            : f.sequenceOntology);
   }
 
-  public static void setInstance(SequenceOntologyI so)
+  /**
+   * For testng only
+   * 
+   * @param so
+   */
+  public static void setSequenceOntology(SequenceOntologyI so)
   {
-    instance = so;
+    getInstance().sequenceOntology = so;
   }
+
+
+  private SequenceOntologyI sequenceOntology;
+
 }
index 0a582e5..402ffdf 100644 (file)
@@ -365,7 +365,7 @@ public class Sequencemapping extends Rangetype
     }
     bindjvvobj(mapping, sequenceMapping);
     jalview.structure.StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance)
+            .getStructureSelectionManager(Desktop.getInstance())
             .registerMapping(acf);
     // Try to link up any conjugate database references in the two sequences
     // matchConjugateDBRefs(from, to, mapping);
index edaabf1..38ad437 100755 (executable)
@@ -25,9 +25,10 @@ import jalview.analysis.GeneticCodeI;
 import jalview.analysis.GeneticCodes;
 import jalview.api.SplitContainerI;
 import jalview.bin.Cache;
-import jalview.bin.Jalview;
 import jalview.gui.JvSwingUtils;
+import jalview.gui.PaintRefresher;
 import jalview.gui.Preferences;
+import jalview.gui.TreePanel;
 import jalview.io.FileFormats;
 import jalview.schemes.ResidueColourScheme;
 import jalview.util.MessageManager;
@@ -35,6 +36,7 @@ import jalview.util.Platform;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
+import java.awt.Component;
 import java.awt.GridLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -44,7 +46,9 @@ import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.swing.BorderFactory;
@@ -222,7 +226,7 @@ public class GAlignFrame extends JInternalFrame
     {
 
       // for Web-page embedding using id=align-frame-div
-      setName(Jalview.getAppID("alignment"));
+      setName(Platform.getAppID("alignment"));
 
 
       jbInit();
@@ -1172,6 +1176,24 @@ public class GAlignFrame extends JInternalFrame
       @Override
       public void menuSelected(MenuEvent e)
       {
+        enableSortMenuOptions();
+      }
+
+      @Override
+      public void menuDeselected(MenuEvent e)
+      {
+      }
+
+      @Override
+      public void menuCanceled(MenuEvent e)
+      {
+      }
+    });
+    sortByTreeMenu.addMenuListener(new MenuListener()
+    {
+      @Override
+      public void menuSelected(MenuEvent e)
+      {
         buildTreeSortMenu();
       }
 
@@ -1712,8 +1734,8 @@ public class GAlignFrame extends JInternalFrame
     });
     JMenuItem selectHighlighted = new JMenuItem(
             MessageManager.getString("action.select_highlighted_columns"));
-    selectHighlighted.setToolTipText(
-            MessageManager.getString("tooltip.select_highlighted_columns"));
+    selectHighlighted.setToolTipText(JvSwingUtils.wrapTooltip(true, 
+            MessageManager.getString("tooltip.select_highlighted_columns")));
     al = new ActionListener()
     {
       @Override
@@ -1918,6 +1940,10 @@ public class GAlignFrame extends JInternalFrame
     // selectMenu.add(listenToViewSelections);
   }
 
+  protected void enableSortMenuOptions()
+  {
+  }
+  
   protected void loadVcf_actionPerformed()
   {
   }
@@ -2716,4 +2742,6 @@ public class GAlignFrame extends JInternalFrame
   protected void showComplement_actionPerformed(boolean complement)
   {
   }
+  
+
 }
index 8e30f5a..0c179e8 100755 (executable)
@@ -21,7 +21,6 @@
 package jalview.jbgui;
 
 import jalview.api.AlignmentViewPanel;
-import jalview.bin.Jalview;
 import jalview.io.FileFormatException;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
@@ -46,7 +45,7 @@ import javax.swing.JMenuItem;
 public class GDesktop extends JFrame
 {
 
-  protected static JMenu windowMenu = new JMenu();
+  protected JMenu windowMenu = new JMenu();
 
   JMenuBar desktopMenubar = new JMenuBar();
 
@@ -141,7 +140,7 @@ public class GDesktop extends JFrame
   private void jbInit() throws Exception
   {
 
-    setName(Jalview.getAppID("desktop"));
+    setName(Platform.getAppID("desktop"));
     FileMenu.setText(MessageManager.getString("action.file"));
     HelpMenu.setText(MessageManager.getString("action.help"));
     inputLocalFileMenuItem
index 74b5afd..aa2f549 100755 (executable)
@@ -20,9 +20,9 @@
  */
 package jalview.jbgui;
 
-import jalview.bin.Jalview;
 import jalview.util.ImageMaker.TYPE;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -89,7 +89,7 @@ public class GPCAPanel extends JInternalFrame
 
   private void jbInit() throws Exception
   {
-    setName(Jalview.getAppID("pca"));
+    setName(Platform.getAppID("pca"));
     this.getContentPane().setLayout(new BorderLayout());
     JPanel jPanel2 = new JPanel();
     jPanel2.setLayout(new FlowLayout());
index 60f17ab..08fbea3 100755 (executable)
@@ -2259,7 +2259,7 @@ public class GPreferences extends JPanel
     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);
index a43e504..2ec7051 100755 (executable)
@@ -219,7 +219,7 @@ public class GSequenceLink extends JPanel
       return true;
     }
 
-    JvOptionPane.showInternalMessageDialog(jalview.gui.Desktop.desktop,
+    JvOptionPane.showInternalMessageDialog(jalview.gui.Desktop.getDesktopPane(),
             MessageManager.getString("warn.url_must_contain"),
             MessageManager.getString("label.invalid_url"),
             JvOptionPane.WARNING_MESSAGE);
@@ -228,7 +228,7 @@ public class GSequenceLink extends JPanel
 
   public void notifyDuplicate()
   {
-    JvOptionPane.showInternalMessageDialog(jalview.gui.Desktop.desktop,
+    JvOptionPane.showInternalMessageDialog(jalview.gui.Desktop.getDesktopPane(),
             MessageManager.getString("warn.name_cannot_be_duplicate"),
             MessageManager.getString("label.invalid_name"),
             JvOptionPane.WARNING_MESSAGE);
index e16d63a..1e7cf37 100644 (file)
 package jalview.jbgui;
 
 import jalview.api.structures.JalviewStructureDisplayI;
-import jalview.bin.Jalview;
 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;
@@ -90,7 +90,7 @@ public abstract class GStructureViewer extends JInternalFrame
   private void jbInit() throws Exception
   {
 
-    setName(Jalview.getAppID("structureviewer"));
+    setName(Platform.getAppID("structureviewer"));
 
     JMenuBar menuBar = new JMenuBar();
     this.setJMenuBar(menuBar);
index 0f9c2a2..3aff0e0 100755 (executable)
@@ -20,9 +20,9 @@
  */
 package jalview.jbgui;
 
-import jalview.bin.Jalview;
 import jalview.util.ImageMaker.TYPE;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -93,7 +93,7 @@ public class GTreePanel extends JInternalFrame
 
   private void jbInit() throws Exception
   {
-    setName(Jalview.getAppID("tree"));
+    setName(Platform.getAppID("tree"));
     this.getContentPane().setLayout(borderLayout1);
     this.setBackground(Color.white);
     this.setFont(new java.awt.Font("Verdana", 0, 12));
index 751b297..9303aed 100644 (file)
@@ -28,6 +28,7 @@ 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;
@@ -60,6 +61,7 @@ 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;
@@ -82,7 +84,6 @@ import jalview.schemes.ColourSchemeProperty;
 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.MessageManager;
@@ -162,10 +163,10 @@ 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.lang.reflect.InvocationTargetException;
 import java.math.BigInteger;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -268,6 +269,25 @@ public class Jalview2XML
   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.
@@ -427,7 +447,7 @@ public class Jalview2XML
    * @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")
@@ -449,7 +469,7 @@ public class Jalview2XML
     return fref;
   }
 
-  public SeqFref newAlcodMapRef(final String sref,
+  protected SeqFref newAlcodMapRef(final String sref,
           final AlignedCodonFrame _cf,
           final jalview.datamodel.Mapping _jmap)
   {
@@ -481,7 +501,7 @@ public class Jalview2XML
     return fref;
   }
 
-  public void resolveFrefedSequences()
+  protected void resolveFrefedSequences()
   {
     Iterator<SeqFref> nextFref = frefedSequence.iterator();
     int toresolve = frefedSequence.size();
@@ -626,13 +646,14 @@ public class Jalview2XML
    * 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<>();
 
     /*
@@ -655,21 +676,22 @@ public class Jalview2XML
       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"))
           {
@@ -677,11 +699,17 @@ public class Jalview2XML
           }
 
           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);
           }
         }
@@ -799,10 +827,22 @@ public class Jalview2XML
     }
   }
 
+  /**
+   * 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);
@@ -829,7 +869,7 @@ public class Jalview2XML
    * @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);
@@ -851,7 +891,7 @@ public class Jalview2XML
    * @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)
@@ -1083,7 +1123,7 @@ public class Jalview2XML
            * 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--)
           {
@@ -1235,9 +1275,9 @@ public class Jalview2XML
     {
       // 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++)
         {
@@ -1281,9 +1321,9 @@ public class Jalview2XML
     /*
      * 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)
         {
@@ -1924,11 +1964,11 @@ public class Jalview2XML
           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)
@@ -2384,7 +2424,7 @@ public class Jalview2XML
     if (calcIdParam.getVersion().equals("1.0"))
     {
       final String[] calcIds = calcIdParam.getServiceURL().toArray(new String[0]);
-      Jws2Instance service = Jws2Discoverer.getDiscoverer()
+      Jws2Instance service = Jws2Discoverer.getInstance()
               .getPreferredServiceFor(calcIds);
       if (service != null)
       {
@@ -2723,17 +2763,6 @@ public class Jalview2XML
   }
 
   /**
-   * 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
    * 
    * @param file
@@ -2767,7 +2796,10 @@ public class Jalview2XML
     {
       try
       {
-        SwingUtilities.invokeAndWait(new Runnable()
+// was invokeAndWait
+         
+         // BH 2019 -- can't wait
+        SwingUtilities.invokeLater(new Runnable()
         {
           @Override
           public void run()
@@ -2780,55 +2812,55 @@ public class Jalview2XML
         System.err.println("Error loading alignment: " + x.getMessage());
       }
     }
+    this.jarFile = null;
     return af;
   }
 
        @SuppressWarnings("unused")
-       private jarInputStreamProvider createjarInputStreamProvider(final Object ofile) throws MalformedURLException {
-
-               // BH 2018 allow for bytes already attached to File object
-               try {
-                       String file = (ofile instanceof File ? ((File) ofile).getCanonicalPath() : ofile.toString());
+  private jarInputStreamProvider createjarInputStreamProvider(
+          final Object ofile) throws MalformedURLException
+  {
+    try
+    {
+      String file = (ofile instanceof File
+              ? ((File) ofile).getCanonicalPath()
+              : ofile.toString());
       byte[] bytes = Platform.isJS() ? Platform.getFileBytes((File) ofile)
               : null;
-                       URL url = null;
-                       errorMessage = null;
-                       uniqueSetSuffix = null;
-                       seqRefIds = null;
-                       viewportsAdded.clear();
-                       frefedSequence = null;
-
-                       if (file.startsWith("http://")) {
-                               url = new URL(file);
-                       }
-                       final URL _url = url;
-                       return new jarInputStreamProvider() {
-
-                               @Override
-                               public JarInputStream getJarInputStream() throws IOException {
-                                       if (bytes != null) {
-//                                             System.out.println("Jalview2XML: opening byte jarInputStream for bytes.length=" + bytes.length);
-                                               return new JarInputStream(new ByteArrayInputStream(bytes));
-                                       }
-                                       if (_url != null) {
-//                                             System.out.println("Jalview2XML: opening url jarInputStream for " + _url);
-                                               return new JarInputStream(_url.openStream());
-                                       } else {
-//                                             System.out.println("Jalview2XML: opening file jarInputStream for " + file);
-                                               return new JarInputStream(new FileInputStream(file));
-                                       }
-                               }
-
-                               @Override
-                               public String getFilename() {
-                                       return file;
-                               }
-                       };
-               } catch (IOException e) {
-                       e.printStackTrace();
-                       return null;
-               }
-       }
+      if (bytes != null)
+      {
+        this.jarFile = (File) ofile;
+      }
+      errorMessage = null;
+      uniqueSetSuffix = null;
+      seqRefIds = null;
+      viewportsAdded.clear();
+      frefedSequence = null;
+
+      URL url = file.startsWith("http://") ? new URL(file) : null;
+      return new jarInputStreamProvider()
+      {
+        @Override
+        public JarInputStream getJarInputStream() throws IOException
+        {
+          InputStream is = bytes != null ? new ByteArrayInputStream(bytes)
+                  : (url != null ? url.openStream()
+                          : new FileInputStream(file));
+          return new JarInputStream(is);
+        }
+
+        @Override
+        public String getFilename()
+        {
+          return file;
+        }
+      };
+    } catch (IOException e)
+    {
+      e.printStackTrace();
+      return null;
+    }
+  }
 
   /**
    * Recover jalview session from a jalview project archive. Caller may
@@ -2854,12 +2886,19 @@ public class Jalview2XML
     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
     Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
     final String file = jprovider.getFilename();
+
+    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();
@@ -2867,9 +2906,27 @@ public class Jalview2XML
         {
           jarentry = jin.getNextJarEntry();
         }
+        String name = (jarentry == null ? null : jarentry.getName());
 
-        if (jarentry != null && jarentry.getName().endsWith(".xml"))
+//        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()
@@ -2877,14 +2934,25 @@ public class Jalview2XML
           javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
           JAXBElement<JalviewModel> jbe = um
                   .unmarshal(streamReader, JalviewModel.class);
-          JalviewModel object = jbe.getValue();
+          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, 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
@@ -2927,9 +2995,9 @@ public class Jalview2XML
       {
         // 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)
       {
@@ -2946,6 +3014,13 @@ public class Jalview2XML
       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);
+      }
+
     }
 
     /*
@@ -2957,7 +3032,7 @@ public class Jalview2XML
      */
     for (AlignFrame fr : gatherToThisFrame.values())
     {
-      Desktop.instance.gatherViews(fr);
+      Desktop.getInstance().gatherViews(fr);
     }
 
     restoreSplitFrames();
@@ -2965,8 +3040,7 @@ public class Jalview2XML
     {
       if (ds.getCodonFrames() != null)
       {
-        StructureSelectionManager
-                .getStructureSelectionManager(Desktop.instance)
+        Desktop.getStructureSelectionManager()
                 .registerMappings(ds.getCodonFrames());
       }
     }
@@ -2975,9 +3049,9 @@ public class Jalview2XML
       reportErrors();
     }
 
-    if (Desktop.instance != null)
+    if (Desktop.getInstance() != null)
     {
-      Desktop.instance.stopLoading();
+      Desktop.getInstance().stopLoading();
     }
 
     return af;
@@ -3058,7 +3132,7 @@ public class Jalview2XML
      */
     for (SplitFrame sf : gatherTo)
     {
-      Desktop.instance.gatherViews(sf);
+      Desktop.getInstance().gatherViews(sf);
     }
 
     splitFrameCandidates.clear();
@@ -3117,7 +3191,7 @@ public class Jalview2XML
           @Override
           public void run()
           {
-            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+            JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                     finalErrorMessage,
                     "Error " + (saving ? "saving" : "loading")
                             + " Jalview file",
@@ -3178,25 +3252,25 @@ public class Jalview2XML
    * @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
     {
@@ -3276,7 +3350,9 @@ public class Jalview2XML
   }
 
   /**
-   * 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
@@ -3294,6 +3370,7 @@ public class Jalview2XML
     SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet().get(0);
     List<Sequence> vamsasSeqs = vamsasSet.getSequence();
 
+
     // JalviewModelSequence jms = object.getJalviewModelSequence();
 
     // Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
@@ -3589,8 +3666,7 @@ public class Jalview2XML
             {
               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)
@@ -3603,6 +3679,7 @@ public class Jalview2XML
             }
           }
         }
+
       }
     } // end !multipleview
 
@@ -3740,6 +3817,7 @@ public class Jalview2XML
             }
           }
         }
+        // create the new AlignmentAnnotation
         jalview.datamodel.AlignmentAnnotation jaa = null;
 
         if (annotation.isGraph())
@@ -3776,6 +3854,7 @@ public class Jalview2XML
           jaa._linecolour = firstColour;
         }
         // register new annotation
+        // Annotation graphs such as Conservation will not have id.
         if (annotation.getId() != null)
         {
           annotationIds.put(annotation.getId(), jaa);
@@ -3987,8 +4066,6 @@ public class Jalview2XML
     // ///////////////////////////////
     // 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)
     {
@@ -4027,8 +4104,9 @@ public class Jalview2XML
     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
@@ -4038,25 +4116,27 @@ public class Jalview2XML
       {
         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,
               uniqueSeqSetId, viewId, autoAlan);
       av = af.getViewport();
+      // note that this only retrieves the most recently accessed
+      // tab of an AlignFrame.
       ap = af.alignPanel;
     }
 
@@ -4065,14 +4145,61 @@ public class Jalview2XML
      * 
      * 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;
   }
 
@@ -4089,7 +4216,7 @@ public class Jalview2XML
    * @param jseqs
    * @param ap
    */
-  private void loadRnaViewers(jarInputStreamProvider jprovider,
+  protected void loadRnaViewers(jarInputStreamProvider jprovider,
           List<JSeq> jseqs, AlignmentPanel ap)
   {
     /*
@@ -4199,6 +4326,12 @@ public class Jalview2XML
                   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 ?
@@ -4219,13 +4352,6 @@ public class Jalview2XML
           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);
 
@@ -4308,7 +4434,7 @@ public class Jalview2XML
             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());
@@ -4550,7 +4676,8 @@ public class Jalview2XML
           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
           filedat = oldFiles.get(new File(reformatedOldFilename));
         }
-        newFileLoc.append(Platform.escapeBackslashes(filedat.getFilePath()));
+        newFileLoc
+                .append(Platform.escapeBackslashes(filedat.getFilePath()));
         pdbfilenames.add(filedat.getFilePath());
         pdbids.add(filedat.getPdbId());
         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
@@ -4627,9 +4754,11 @@ public class Jalview2XML
     final AlignFrame alf = af;
     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
             svattrib.getWidth(), svattrib.getHeight());
-    try
-    {
-      javax.swing.SwingUtilities.invokeAndWait(new Runnable()
+    
+    // BH again was invokeAndWait
+    // try
+    // {
+      javax.swing.SwingUtilities.invokeLater(new Runnable()
       {
         @Override
         public void run()
@@ -4656,14 +4785,14 @@ public class Jalview2XML
           }
         }
       });
-    } catch (InvocationTargetException ex)
-    {
-      warn("Unexpected error when opening Jmol view.", ex);
-
-    } catch (InterruptedException e)
-    {
-      // e.printStackTrace();
-    }
+    // } catch (InvocationTargetException ex)
+    // {
+    // warn("Unexpected error when opening Jmol view.", ex);
+    //
+    // } catch (InterruptedException e)
+    // {
+    // // e.printStackTrace();
+    // }
 
   }
 
@@ -4792,7 +4921,7 @@ public class Jalview2XML
     {
       try
       {
-        frames = Desktop.desktop.getAllFrames();
+        frames = Desktop.getDesktopPane().getAllFrames();
       } catch (ArrayIndexOutOfBoundsException e)
       {
         // occasional No such child exceptions are thrown here...
@@ -4862,27 +4991,28 @@ public class Jalview2XML
     }
   }
 
-  AlignFrame loadViewport(String file, List<JSeq> JSEQ,
-          List<SequenceI> hiddenSeqs, AlignmentI al,
-          JalviewModel jm, Viewport view, String uniqueSeqSetId,
-          String viewId, List<JvAnnotRow> autoAlan)
+  AlignFrame loadViewport(String fileName, List<JSeq> JSEQ,
+          List<SequenceI> hiddenSeqs, AlignmentI al, JalviewModel jm,
+          Viewport view, String uniqueSeqSetId, String viewId,
+          List<JvAnnotRow> autoAlan)
   {
     AlignFrame af = null;
     af = new AlignFrame(al, safeInt(view.getWidth()),
-            safeInt(view.getHeight()), uniqueSeqSetId, viewId) 
-//    {
-//     
-//     @Override
-//     protected void processKeyEvent(java.awt.event.KeyEvent e) {
-//             System.out.println("Jalview2XML   AF " + e);
-//             super.processKeyEvent(e);
-//             
-//     }
-//     
-//    }
+            safeInt(view.getHeight()), uniqueSeqSetId, viewId)
+    // {
+    //
+    // @Override
+    // protected void processKeyEvent(java.awt.event.KeyEvent e) {
+    // System.out.println("Jalview2XML AF " + e);
+    // super.processKeyEvent(e);
+    //
+    // }
+    //
+    // }
     ;
-
-    af.setFileName(file, FileFormat.Jalview);
+    af.alignPanel.setHoldRepaint(true);
+    af.setFileName(fileName, FileFormat.Jalview);
+    af.setFileObject(jarFile); // BH 2019 JAL-3436
 
     final AlignViewport viewport = af.getViewport();
     for (int i = 0; i < JSEQ.size(); i++)
@@ -4957,9 +5087,8 @@ public class Jalview2XML
 
     viewport.setColourText(safeBoolean(view.isShowColourText()));
 
-    viewport
-            .setConservationSelected(
-                    safeBoolean(view.isConservationSelected()));
+    viewport.setConservationSelected(
+            safeBoolean(view.isConservationSelected()));
     viewport.setIncrement(safeInt(view.getConsThreshold()));
     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
@@ -4990,8 +5119,18 @@ public class Jalview2XML
       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;
@@ -5035,9 +5174,8 @@ public class Jalview2XML
     af.changeColour(cs);
     viewport.setColourAppliesToAllGroups(true);
 
-    viewport
-            .setShowSequenceFeatures(
-                    safeBoolean(view.isShowSequenceFeatures()));
+    viewport.setShowSequenceFeatures(
+            safeBoolean(view.isShowSequenceFeatures()));
 
     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
@@ -5057,17 +5195,17 @@ public class Jalview2XML
     // recover feature settings
     if (jm.getFeatureSettings() != null)
     {
-      FeatureRendererModel fr = af.alignPanel.getSeqPanel().seqCanvas
+      FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
               .getFeatureRenderer();
       FeaturesDisplayed fdi;
       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
-      String[] renderOrder = new String[jm.getFeatureSettings()
-              .getSetting().size()];
+      String[] renderOrder = new String[jm.getFeatureSettings().getSetting()
+              .size()];
       Map<String, FeatureColourI> featureColours = new Hashtable<>();
       Map<String, Float> featureOrder = new Hashtable<>();
 
-      for (int fs = 0; fs < jm.getFeatureSettings()
-              .getSetting().size(); fs++)
+      for (int fs = 0; fs < jm.getFeatureSettings().getSetting()
+              .size(); fs++)
       {
         Setting setting = jm.getFeatureSettings().getSetting().get(fs);
         String featureType = setting.getType();
@@ -5079,8 +5217,8 @@ public class Jalview2XML
                 .getMatcherSet();
         if (filters != null)
         {
-          FeatureMatcherSetI filter = Jalview2XML
-                  .parseFilter(featureType, filters);
+          FeatureMatcherSetI filter = Jalview2XML.parseFilter(featureType,
+                  filters);
           if (!filter.isEmpty())
           {
             fr.setFeatureFilter(featureType, filter);
@@ -5112,8 +5250,7 @@ public class Jalview2XML
           float max = setting.getMax() == null ? 1f
                   : setting.getMax().floatValue();
           FeatureColourI gc = new FeatureColour(maxColour, minColour,
-                  maxColour,
-                  noValueColour, min, max);
+                  maxColour, noValueColour, min, max);
           if (setting.getAttributeName().size() > 0)
           {
             gc.setAttributeName(setting.getAttributeName().toArray(
@@ -5147,8 +5284,7 @@ public class Jalview2XML
         }
         else
         {
-          featureColours.put(featureType,
-                  new FeatureColour(maxColour));
+          featureColours.put(featureType, new FeatureColour(maxColour));
         }
         renderOrder[fs] = featureType;
         if (setting.getOrder() != null)
@@ -5485,7 +5621,7 @@ public class Jalview2XML
     return false;
   }
 
-  public void addToSkipList(AlignFrame af)
+  protected void addToSkipList(AlignFrame af)
   {
     if (skipList == null)
     {
@@ -5494,7 +5630,7 @@ public class Jalview2XML
     skipList.put(af.getViewport().getSequenceSetId(), af);
   }
 
-  public void clearSkipList()
+  protected void clearSkipList()
   {
     if (skipList != null)
     {
@@ -5563,8 +5699,8 @@ public class Jalview2XML
       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.
@@ -5994,6 +6130,8 @@ public class Jalview2XML
     AlignFrame af = loadFromObject(jm, 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;
@@ -6351,7 +6489,11 @@ public class Jalview2XML
           panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
                   axis.getXPos(), axis.getYPos(), axis.getZPos());
         }
-        PCAPanel.addToDesktop(panel, modelName);
+
+        Dimension dim = Platform.getDimIfEmbedded(panel, 475, 450);
+        Desktop.addInternalFrame(panel, MessageManager.formatMessage(
+                "label.calc_title", "PCA", modelName), dim.width,
+                dim.height);
       }
     } catch (Exception ex)
     {
index a37882f..7c8c9a6 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.rest;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.httpserver.AbstractRequestHandler;
 
 import java.io.IOException;
@@ -30,20 +32,16 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 /**
- * A simple handler to process (or delegate) HTTP requests on /jalview/rest
+ * A simple handler to process (or delegate) HTTP requests on /jalview/rest.
  */
 public class RestHandler extends AbstractRequestHandler
+        implements ApplicationSingletonI
 {
   private static final String MY_PATH = "rest";
 
   private static final String MY_NAME = "Rest";
 
   /**
-   * Singleton instance of this class
-   */
-  private static RestHandler instance = null;
-
-  /**
    * Returns the singleton instance of this class
    * 
    * @return
@@ -53,12 +51,8 @@ public class RestHandler extends AbstractRequestHandler
   {
     synchronized (RestHandler.class)
     {
-      if (instance == null)
-      {
-        instance = new RestHandler();
-      }
+      return (RestHandler) ApplicationSingletonProvider.getInstance(RestHandler.class);
     }
-    return instance;
   }
 
   /**
index d31fbba..0ff7c6e 100644 (file)
 package jalview.schemes;
 
 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
@@ -47,7 +42,8 @@ public class ColourSchemes
    */
   public static ColourSchemes getInstance()
   {
-    return instance;
+    return (ColourSchemes) ApplicationSingletonProvider
+            .getInstance(ColourSchemes.class);
   }
 
   private ColourSchemes()
@@ -56,6 +52,51 @@ public class 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
index 9662fee..b5672ab 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.structure;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.PDBEntry.Type;
 
@@ -30,27 +32,39 @@ 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 static boolean processSecStr = false;
+  private 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 static boolean showSeqFeatures = true;
+  private boolean showSeqFeatures = true;
 
   public enum StructureParser
   {
@@ -61,92 +75,93 @@ public class StructureImportSettings
    * Determines the default file format for structure files to be downloaded
    * from the PDB sequence fetcher. Possible options include: PDB|mmCIF
    */
-  private static PDBEntry.Type defaultStructureFileFormat = Type.PDB;
+  private PDBEntry.Type defaultStructureFileFormat = Type.PDB;
 
   /**
    * 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());
   }
 
   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());
   }
 
index 8c3816e..f8048cd 100644 (file)
  */
 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;
@@ -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;
@@ -84,6 +85,67 @@ public class StructureSelectionManager
 
   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
@@ -198,49 +260,6 @@ public class StructureSelectionManager
             || 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
@@ -270,7 +289,7 @@ public class StructureSelectionManager
     return relaySeqMappings;
   }
 
-  Vector listeners = new Vector();
+  Vector<Object> listeners = new Vector<>();
 
   /**
    * register a listener for alignment sequence mouseover events
@@ -308,6 +327,8 @@ public class StructureSelectionManager
    * 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
@@ -332,8 +353,11 @@ public class StructureSelectionManager
    * 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
@@ -377,7 +401,7 @@ public class StructureSelectionManager
    *          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)
@@ -652,7 +676,6 @@ public class StructureSelectionManager
         {
           ds = ds.getDatasetSequence();
         }
-        ;
         if (ds.getAnnotation() != null)
         {
           for (AlignmentAnnotation ala : ds.getAnnotation())
@@ -906,9 +929,9 @@ public class StructureSelectionManager
         if (s != null)
         {
           result = s;
-        }
       }
     }
+  }
     return result;
   }
 
@@ -1242,8 +1265,11 @@ public class StructureSelectionManager
   }
 
   /**
-   * 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()
   {
@@ -1281,7 +1307,11 @@ public class StructureSelectionManager
     }
   }
 
-  public void addSelectionListener(SelectionListener selecter)
+  public List<SelectionListener> getListeners() {
+    return sel_listeners;
+  }
+  
+   public void addSelectionListener(SelectionListener selecter)
   {
     if (!sel_listeners.contains(selecter))
     {
@@ -1329,41 +1359,23 @@ public class StructureSelectionManager
         {
           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
index 7dd1a19..7c38ab6 100644 (file)
 
 package jalview.urls;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
+
 /**
  * Holds settings for identifiers.org e.g. url, download location
- * 
- * @author $author$
- * @version $Revision$
  */
-public class IdOrgSettings
+public class IdOrgSettings implements ApplicationSingletonI
 {
-  private static String url;
+  private String url;
+
+  private String location;
+
+  private static IdOrgSettings getInstance()
+  {
+    return (IdOrgSettings) ApplicationSingletonProvider
+            .getInstance(IdOrgSettings.class);
+  }
 
-  private static String location;
+  private IdOrgSettings()
+  {
+    // private singleton
+  }
 
   public static void setUrl(String seturl)
   {
-    url = seturl;
+    getInstance().url = seturl;
   }
 
   public static void setDownloadLocation(String setloc)
   {
-    location = setloc;
+    getInstance().location = setloc;
   }
 
   public static String getUrl()
   {
-    return url;
+    return getInstance().url;
   }
 
   public static String getDownloadLocation()
   {
-    return location;
+    return getInstance().location;
   }
 }
index 07eb23e..e898438 100644 (file)
@@ -118,11 +118,6 @@ private HashMap<String, UrlLink> readIdentifiers(String idFileName)
       }
     } catch (IOException | ParseException e)
     {
-      // unnecessary e.printStackTrace();
-      // Note how in JavaScript we can grab the first bytes from any file reader. 
-      // Typical report here is "NetworkError" because the file does not exist.
-      // "https://." is coming from System.getProperty("user.home"), but this could
-      // be set by the page developer to anything, of course.
       errorMessage = e.toString();
       idData.clear();
     }
index bb94566..2a07616 100644 (file)
@@ -57,8 +57,7 @@ public class MessageManager
       // 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
@@ -94,7 +93,7 @@ public class MessageManager
     } catch (Exception e)
     {
       String msg = "I18N missing: " + loc + "\t" + key;
-         logWarning(key, msg);
+    logWarning(key, msg);
     }
     return value;
   }
@@ -169,8 +168,8 @@ public class MessageManager
     } catch (Exception x)
     {
       String msg = "I18N missing key with root " + keyroot + ": " + loc + "\t"
-                         + smkey;
-         logWarning(smkey, msg);
+              + smkey;
+    logWarning(smkey, msg);
     }
     return name;
   }
@@ -183,10 +182,10 @@ public class MessageManager
    */
   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);
+  }
   }
 }
index 6dbe549..371cd32 100644 (file)
@@ -22,9 +22,7 @@ package jalview.util;
 
 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,8 +32,17 @@ 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;
@@ -48,6 +55,7 @@ import org.json.simple.parser.ParseException;
 
 import com.stevesoft.pat.Regex;
 
+import jalview.bin.Jalview;
 import jalview.javascript.json.JSON;
 import swingjs.api.JSUtilI;
 
@@ -56,7 +64,6 @@ import swingjs.api.JSUtilI;
  * 
  * @author Jim Procter
  */
-
 public class Platform
 {
 
@@ -69,7 +76,6 @@ public class Platform
   private static Boolean isHeadless = null;
 
   private static swingjs.api.JSUtilI jsutil;
-  
 
   static
   {
@@ -77,6 +83,9 @@ public class Platform
     {
       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)
@@ -230,14 +239,14 @@ public class Platform
    */
   protected static boolean isControlDown(MouseEvent e, boolean aMac)
   {
-    
+
     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.
   // Notice the distinction in mouse events. (BUTTON3_MASK == META)
@@ -638,9 +647,14 @@ public class Platform
 
   }
 
+  /**
+   * Allow for URL-line command arguments. Untested.
+   * 
+   */
   public static void getURLCommandArguments()
   {
 
+    try {
     /**
      * Retrieve the first query field as command arguments to Jalview. Include
      * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's __Info.args
@@ -650,8 +664,11 @@ public class Platform
      *            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);
      */
-
+    } catch (Throwable t) {
+    }
   }
 
   /**
@@ -804,17 +821,17 @@ public class Platform
   {
     if (isJS)
     {
-      jsutil.setAppletAttribute("app", j);
+      jsutil.setAppClass(j);
     }
   }
 
   /**
    *
-   * If this frame ia embedded in a web page, return a known type.
+   * If this frame is embedded in a web page, return a known type.
    * 
    * @param frame
    *          a JFrame or JInternalFrame
-   * @param type
+   * @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)
@@ -844,4 +861,160 @@ public class Platform
     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());
+      }
+    }
+  }
+
 }
index 4f399f0..4148c72 100644 (file)
@@ -83,6 +83,9 @@ 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();
@@ -605,10 +608,31 @@ public abstract class AlignmentViewport
 
   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();
@@ -824,7 +848,7 @@ public abstract class AlignmentViewport
     // see note in mantis : issue number 8585
     if (alignment.isNucleotide()
             || (conservation == null && quality == null)
-            || !autoCalculateConsensus)
+            || !autoCalculateConsensusAndConservation)
     {
       return;
     }
@@ -842,7 +866,7 @@ public abstract class AlignmentViewport
   public void updateConsensus(final AlignmentViewPanel ap)
   {
     // see note in mantis : issue number 8585
-    if (consensus == null || !autoCalculateConsensus)
+    if (consensus == null || !autoCalculateConsensusAndConservation)
     {
       return;
     }
@@ -1275,21 +1299,22 @@ public abstract class AlignmentViewport
    * 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;
   }
 
@@ -1350,22 +1375,6 @@ public abstract class AlignmentViewport
     }
   }
 
-  /**
-   * 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()
@@ -1430,13 +1439,14 @@ public abstract class AlignmentViewport
 
       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();
@@ -1459,8 +1469,7 @@ public abstract class AlignmentViewport
       }
 
       ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
-
-      firePropertyChange("alignment", null, alignment.getSequences());
+      notifyAlignment();
       sendSelection();
     }
   }
@@ -1494,7 +1503,7 @@ public abstract class AlignmentViewport
         setSequenceAnnotationsVisible(seq[i], false);
       }
       ranges.setStartSeq(startSeq);
-      firePropertyChange("alignment", null, alignment.getSequences());
+      notifyAlignment();
     }
   }
 
@@ -1859,11 +1868,11 @@ public abstract class AlignmentViewport
     {
       alignment.padGaps();
     }
-    if (autoCalculateConsensus)
+    if (autoCalculateConsensusAndConservation)
     {
       updateConsensus(ap);
     }
-    if (hconsensus != null && autoCalculateConsensus)
+    if (hconsensus != null && autoCalculateConsensusAndConservation)
     {
       updateConservation(ap);
     }
@@ -3078,4 +3087,22 @@ public abstract class AlignmentViewport
       codingComplement.setUpdateStructures(needToUpdateStructureViews);
     }
   }
+  
+
+  /**
+   * 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);
+  }
+
 }
index 4f671da..b96ac28 100644 (file)
@@ -112,7 +112,7 @@ public class ViewportRanges extends ViewportProperties
    * Set first residue visible in the viewport, and retain the current width.
    * Fires a property change event.
    * 
-   * @param res
+   * @param res 
    *          residue position
    */
   public void setStartRes(int res)
@@ -136,13 +136,25 @@ public class ViewportRanges extends ViewportProperties
     int[] oldvalues = updateStartEndRes(start, end);
     int oldstartres = oldvalues[0];
     int oldendres = oldvalues[1];
+    
+    if (oldstartres == startRes && oldendres == endRes)
+    {
+      return; // BH 2019.07.27 standard check for no changes
+    }
 
+    // "STARTRES" is a misnomer here -- really "STARTORENDRES"
+    // note that this could be "no change" if the range is just being expanded
     changeSupport.firePropertyChange(STARTRES, oldstartres, startRes);
     if (oldstartres == startRes)
     {
+      // only caught in ViewportRangesTest
+      // No listener cares about this
+      // "ENDRES" is a misnomer here -- really "ENDONLYRES"
+      // BH 2019.07.27 adds end change check
+      // fire only if only the end is changed
       // event won't be fired if start positions are same
       // fire an event for the end positions in case they changed
-      changeSupport.firePropertyChange(ENDRES, oldendres, endRes);
+     changeSupport.firePropertyChange(ENDRES, oldendres, endRes);
     }
   }
 
@@ -224,7 +236,6 @@ public class ViewportRanges extends ViewportProperties
    */
   public void setStartEndSeq(int start, int end)
   {
-    // System.out.println("ViewportRange setStartEndSeq " + start + " " + end);
     int[] oldvalues = updateStartEndSeq(start, end);
     int oldstartseq = oldvalues[0];
     int oldendseq = oldvalues[1];
@@ -250,34 +261,16 @@ public class ViewportRanges extends ViewportProperties
    */
   private int[] updateStartEndSeq(int start, int end)
   {
-    int oldstartseq = this.startSeq;
-    int visibleHeight = getVisibleAlignmentHeight();
-    if (start > visibleHeight - 1)
-    {
-      startSeq = Math.max(visibleHeight - 1, 0);
-    }
-    else if (start < 0)
-    {
-      startSeq = 0;
-    }
-    else
-    {
-      startSeq = start;
-    }
 
+//    if (end == 3 && this.endSeq == 14 || end == 13 && this.endSeq == 3) {
+//      new NullPointerException().printStackTrace(System.out);
+//      System.out.println("ViewportRange updateStartEndSeq " + start + " " + end + " " + Thread.currentThread());
+//    }
+    int oldstartseq = this.startSeq;
     int oldendseq = this.endSeq;
-    if (end >= visibleHeight)
-    {
-      endSeq = Math.max(visibleHeight - 1, 0);
-    }
-    else if (end < 0)
-    {
-      endSeq = 0;
-    }
-    else
-    {
-      endSeq = end;
-    }
+    int max = getVisibleAlignmentHeight() - 1;
+    startSeq = Math.max(0,  Math.min(start, max));
+    endSeq = Math.max(0, Math.min(end, max));
     return new int[] { oldstartseq, oldendseq };
   }
 
@@ -392,21 +385,15 @@ public class ViewportRanges extends ViewportProperties
    */
   public void setViewportStartAndWidth(int start, int w)
   {
-    int vpstart = start;
-    if (vpstart < 0)
-    {
-      vpstart = 0;
-    }
-
-    /*
-     * if not wrapped, don't leave white space at the right margin
-     */
+    int vpstart = Math.max(0, start);
+     
     if (!wrappedMode)
     {
-      if ((w <= getVisibleAlignmentWidth())
-              && (vpstart + w - 1 > getVisibleAlignmentWidth() - 1))
+      // if not wrapped, don't leave white space at the right margin
+      int maxStart = getVisibleAlignmentWidth() - w;
+      if (maxStart >= 0)
       {
-        vpstart = getVisibleAlignmentWidth() - w;
+        vpstart = Math.min(vpstart, maxStart);
       }
 
     }
@@ -425,22 +412,15 @@ public class ViewportRanges extends ViewportProperties
    */
   public void setViewportStartAndHeight(int start, int h)
   {
-    int vpstart = start;
-
-    int visHeight = getVisibleAlignmentHeight();
-    if (vpstart < 0)
-    {
-      vpstart = 0;
-    }
-    else if (h <= visHeight && vpstart + h > visHeight)
-    // viewport height is less than the full alignment and we are running off
-    // the bottom
+    int vpstart = Math.max(0, start);
+    int maxStart = getVisibleAlignmentHeight() - h;
+    if (maxStart > 0)
     {
-      vpstart = visHeight - h;
+      // can't start higher than vertical extent will allow
+      // (viewport height is less than the full alignment
+      // and we are running off the bottom)
+      vpstart = Math.min(vpstart, maxStart);
     }
-    // System.out.println("ViewportRanges setviewportStartAndHeight " + vpstart
-    // + " " + start + " " + h + " " + getVisibleAlignmentHeight());
-
     setStartEndSeq(vpstart, vpstart + h - 1);
   }
 
@@ -797,4 +777,10 @@ public class ViewportRanges extends ViewportProperties
 
     return maxScroll;
   }
+  
+
+  @Override
+  public String toString() {
+     return "[ViewportRange " + startSeq + "-" + endSeq + ", "+ startRes + "-" + endRes + "]";
+  }
 }
index 08ef3a2..c596f05 100644 (file)
@@ -207,6 +207,10 @@ public class AlignCalcManager implements AlignCalcManagerI
     }
   }
 
+  public int getQueueLength() {
+    return inProgress.size();
+  }
+  
   @Override
   public void registerWorker(AlignCalcWorkerI worker)
   {
index a480176..5c94faf 100644 (file)
@@ -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;
@@ -39,8 +41,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
index 9cc4960..25144fc 100644 (file)
@@ -24,29 +24,33 @@ import jalview.ws.seqfetcher.ASequenceFetcher;
 
 public class SequenceFetcherFactory
 {
-
-  private static SequenceFetcher instance;
-
-  /**
-   * Returns a new SequenceFetcher object, or a mock object if one has been set
-   * 
-   * @return
-   */
-  public static ASequenceFetcher getSequenceFetcher()
-  {
-    return instance == null ? new SequenceFetcher() : instance;
-  }
-
-  /**
-   * 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 setSequenceFetcher(SequenceFetcher sf)
-  {
-    instance = sf;
-  }
+  // BH the two methods in this class merged into SequenceFetcher
 }
+//public class SequenceFetcherFactory
+//{
+//
+//  private static SequenceFetcher instance;
+//
+//  /**
+//   * Returns a new SequenceFetcher object, or a mock object if one has been set
+//   * 
+//   * @return
+//   */
+//  public static ASequenceFetcher getSequenceFetcher()
+//  {
+//    return instance == null ? new SequenceFetcher() : instance;
+//  }
+//
+//  /**
+//   * 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 setSequenceFetcher(SequenceFetcher sf)
+//  {
+//    instance = sf;
+//  }
+//}
index bee9fad..6aa3f48 100644 (file)
  */
 package jalview.ws.jws1;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
+import jalview.gui.AlignmentPanel;
 import jalview.gui.JvOptionPane;
 import jalview.util.MessageManager;
 
 import java.net.URL;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.StringTokenizer;
 import java.util.Vector;
 
@@ -34,8 +38,19 @@ import ext.vamsas.RegistryServiceSoapBindingStub;
 import ext.vamsas.ServiceHandle;
 import ext.vamsas.ServiceHandles;
 
-public class Discoverer implements Runnable
+public class Discoverer implements Runnable, ApplicationSingletonI
 {
+
+  public static Discoverer getInstance()
+  {
+    return (Discoverer) ApplicationSingletonProvider.getInstance(Discoverer.class);
+  }
+
+  private Discoverer()
+  {
+    // use getInstance()
+  }
+
   ext.vamsas.IRegistry registry; // the root registry service.
 
   private java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
@@ -110,20 +125,24 @@ public class Discoverer implements Runnable
     return server;
   }
 
-  static private java.net.URL RootServiceURL = null;
+  private java.net.URL RootServiceURL = null;
 
-  static public Vector<URL> ServiceURLList = null;
+  private Vector<URL> ServiceURLList = null;
 
-  static private boolean reallyDiscoverServices = true;
+  public Vector<URL> getServiceURLList() {
+    return ServiceURLList;
+  }
+  
+  private boolean reallyDiscoverServices = true;
 
-  public static java.util.Hashtable<String, Vector<ServiceHandle>> services = null;
+  private java.util.Hashtable<String, Vector<ServiceHandle>> services = null;
   // stored by
   // abstractServiceType
   // string
 
-  public static java.util.Vector<ServiceHandle> serviceList = null;
+  public java.util.Vector<ServiceHandle> serviceList = null;
 
-  static private Vector<URL> getDiscoveryURLS()
+  private Vector<URL> getDiscoveryURLS()
   {
     Vector<URL> urls = new Vector<>();
     String RootServiceURLs = jalview.bin.Cache.getDefault("DISCOVERY_URLS",
@@ -176,10 +195,16 @@ public class Discoverer implements Runnable
    */
   static public void doDiscovery()
   {
+    getInstance().discovery();
+  }
+
+  private void discovery()
+  {
     jalview.bin.Cache.log
             .debug("(Re)-Initialising the discovery URL list.");
     try
     {
+      Discoverer d = getInstance();
       reallyDiscoverServices = jalview.bin.Cache
               .getDefault("DISCOVERY_START", false);
       if (reallyDiscoverServices)
@@ -246,9 +271,9 @@ public class Discoverer implements Runnable
       // JBPNote - should do this a better way!
       if (f.getFaultReason().indexOf("(407)") > -1)
       {
-        if (jalview.gui.Desktop.desktop != null)
+        if (jalview.gui.Desktop.getDesktopPane() != null)
         {
-          JvOptionPane.showMessageDialog(jalview.gui.Desktop.desktop,
+          JvOptionPane.showMessageDialog(jalview.gui.Desktop.getDesktopPane(),
                   MessageManager.getString("label.set_proxy_settings"),
                   MessageManager
                           .getString("label.proxy_authorization_failed"),
@@ -284,7 +309,7 @@ public class Discoverer implements Runnable
    *          Hashtable
    * @return boolean
    */
-  static private boolean buildServiceLists(ServiceHandle[] sh,
+  private boolean buildServiceLists(ServiceHandle[] sh,
           Vector<ServiceHandle> cat,
           Hashtable<String, Vector<ServiceHandle>> sscat)
   {
@@ -395,10 +420,32 @@ public class Discoverer implements Runnable
   /**
    * binding service abstract name to handler class
    */
-  private static Hashtable<String, WS1Client> serviceClientBindings;
+  private Hashtable<String, WS1Client> serviceClientBindings;
 
   public static WS1Client getServiceClient(ServiceHandle sh)
   {
+    return getInstance().getClient(sh);
+  }
+  
+  /**
+   * notes on discovery service 1. need to allow multiple discovery source urls.
+   * 2. user interface to add/control list of urls in preferences notes on
+   * wsclient discovery 1. need a classpath property with list of additional
+   * plugin directories 2. optional config to cite specific bindings between
+   * class name and Abstract service name. 3. precedence for automatic discovery
+   * by using getAbstractName for WSClient - user added plugins override default
+   * plugins ? notes on wsclient gui code for gui attachment now moved to
+   * wsclient implementation. Needs more abstraction but approach seems to work.
+   * is it possible to 'generalise' the data retrieval calls further ? current
+   * methods are very specific (gatherForMSA or gatherForSeqOrMsaSecStrPred),
+   * new methods for conservation (group or alignment), treecalc (aligned
+   * profile), seqannot (sequences selected from dataset, annotation back to
+   * dataset).
+   * 
+   */
+
+  private WS1Client getClient(ServiceHandle sh)
+  {
     if (serviceClientBindings == null)
     {
       // get a list from Config or create below
@@ -421,20 +468,9 @@ public class Discoverer implements Runnable
     }
     return instance;
   }
-  /**
-   * notes on discovery service 1. need to allow multiple discovery source urls.
-   * 2. user interface to add/control list of urls in preferences notes on
-   * wsclient discovery 1. need a classpath property with list of additional
-   * plugin directories 2. optional config to cite specific bindings between
-   * class name and Abstract service name. 3. precedence for automatic discovery
-   * by using getAbstractName for WSClient - user added plugins override default
-   * plugins ? notes on wsclient gui code for gui attachment now moved to
-   * wsclient implementation. Needs more abstraction but approach seems to work.
-   * is it possible to 'generalise' the data retrieval calls further ? current
-   * methods are very specific (gatherForMSA or gatherForSeqOrMsaSecStrPred),
-   * new methods for conservation (group or alignment), treecalc (aligned
-   * profile), seqannot (sequences selected from dataset, annotation back to
-   * dataset).
-   * 
-   */
+
+  public static Hashtable<String, Vector<ServiceHandle>> getServices()
+  {
+    return getInstance().services;
+  }
 }
index 3b7bdb6..1c625fa 100644 (file)
@@ -304,7 +304,7 @@ public class JPredClient extends WS1Client
     WsURL = "http://www.compbio.dundee.ac.uk/JalviewWS/services/jpred";
 
     WebserviceInfo wsInfo = new WebserviceInfo(WebServiceJobTitle,
-            WebServiceReference, true);
+            WebServiceReference, Desktop.FRAME_MAKE_VISIBLE);
 
     return wsInfo;
   }
@@ -323,7 +323,7 @@ public class JPredClient extends WS1Client
 
     } catch (Exception ex)
     {
-      JvOptionPane.showMessageDialog(Desktop.desktop,
+      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
               MessageManager.formatMessage(
                       "label.secondary_structure_prediction_service_couldnt_be_located",
                       new String[]
index 4a09625..e97d309 100644 (file)
@@ -78,7 +78,7 @@ public class MsaWSClient extends WS1Client
     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[]
@@ -91,7 +91,7 @@ public class MsaWSClient extends WS1Client
 
     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"),
index 53338d3..cb965f0 100644 (file)
@@ -84,7 +84,7 @@ public class SeqSearchWSClient extends WS1Client
     // name to service client name
     if (!sh.getAbstractName().equals(this.getServiceActionKey()))
     {
-      JvOptionPane.showMessageDialog(Desktop.desktop,
+      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
               MessageManager.formatMessage(
                       "label.service_called_is_not_seq_search_service",
                       new String[]
@@ -97,7 +97,7 @@ public class SeqSearchWSClient extends WS1Client
 
     if ((wsInfo = setWebService(sh)) == null)
     {
-      JvOptionPane.showMessageDialog(Desktop.desktop,
+      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
               MessageManager.formatMessage(
                       "label.seq_search_service_is_unknown", new String[]
                       { sh.getName() }),
index 6fa41fc..7bffe43 100644 (file)
@@ -21,6 +21,7 @@
 package jalview.ws.jws1;
 
 import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
 import jalview.gui.WebserviceInfo;
 import jalview.util.MessageManager;
 import jalview.ws.WSClient;
@@ -92,8 +93,8 @@ public abstract class WS1Client extends WSClient
     WebserviceInfo wsInfo = null;
     if (!headless)
     {
-      wsInfo = new WebserviceInfo(WebServiceJobTitle, WebServiceReference,
-              true);
+      wsInfo = new WebserviceInfo(WebServiceJobTitle, WebServiceReference, 
+              Desktop.FRAME_MAKE_VISIBLE);
     }
     return wsInfo;
   }
index 0f1a25e..004a845 100644 (file)
@@ -154,8 +154,8 @@ public abstract class Jws2Client extends jalview.ws.WSClient
       return new WebserviceInfo(WebServiceJobTitle,
               WebServiceJobTitle + " using service hosted at "
                       + serv.hosturl + "\n"
-                      + (serv.description != null ? serv.description : ""),
-              false);
+                      + (serv.description != null ? serv.description : ""), 
+                      Desktop.FRAME_NOT_VISIBLE);
     }
     return null;
   }
@@ -398,7 +398,7 @@ public abstract class Jws2Client extends jalview.ws.WSClient
       {
         // check service is actually in the list of currently avaialable
         // services
-        if (!Jws2Discoverer.getDiscoverer().getServices().contains(service))
+        if (!Jws2Discoverer.getInstance().getServices().contains(service))
         {
           // it isn't ..
           service = null;
@@ -408,7 +408,7 @@ public abstract class Jws2Client extends jalview.ws.WSClient
     if (service == null)
     {
       // get the default service for AACon
-      service = Jws2Discoverer.getDiscoverer().getPreferredServiceFor(null,
+      service = Jws2Discoverer.getInstance().getPreferredServiceFor(null,
               aaui.getServiceType());
     }
     if (service == null)
index 516a719..255ef8f 100644 (file)
@@ -21,6 +21,8 @@
 package jalview.ws.jws2;
 
 import jalview.bin.Cache;
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
 import jalview.gui.JvSwingUtils;
@@ -60,8 +62,29 @@ import compbio.ws.client.Services;
  * @author JimP
  * 
  */
-public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
+public class Jws2Discoverer
+        implements Runnable, WSMenuEntryProviderI, ApplicationSingletonI
 {
+
+  /**
+   * Returns the singleton instance of this class.
+   * 
+   * @return
+   */
+  public static Jws2Discoverer getInstance()
+  {
+    return (Jws2Discoverer) ApplicationSingletonProvider
+            .getInstance(Jws2Discoverer.class);
+  }
+
+  /**
+   * Private constructor enforces use of singleton via getDiscoverer()
+   */
+  private Jws2Discoverer()
+  {
+    // use getInstance();
+  }
+
   public static final String COMPBIO_JABAWS = "http://www.compbio.dundee.ac.uk/jabaws";
 
   /*
@@ -70,19 +93,14 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   private final static String JWS2HOSTURLS = "JWS2HOSTURLS";
 
   /*
-   * Singleton instance
-   */
-  private static Jws2Discoverer discoverer;
-
-  /*
    * Override for testing only
    */
-  private static List<String> testUrls = null;
+  private List<String> testUrls = null;
 
   // preferred url has precedence over others
   private String preferredUrl;
 
-  private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
+  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
           this);
 
   private Vector<String> invalidServiceUrls = null;
@@ -103,13 +121,6 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
   protected Vector<Jws2Instance> services;
 
   /**
-   * Private constructor enforces use of singleton via getDiscoverer()
-   */
-  private Jws2Discoverer()
-  {
-  }
-
-  /**
    * change listeners are notified of "services" property changes
    * 
    * @param listener
@@ -190,7 +201,7 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     oldthread = Thread.currentThread();
     try
     {
-      Class foo = getClass().getClassLoader()
+      getClass().getClassLoader()
               .loadClass("compbio.ws.client.Jws2Client");
     } catch (ClassNotFoundException e)
     {
@@ -583,36 +594,36 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
    */
   public static void main(String[] args)
   {
+    Jws2Discoverer instance = getInstance();
     if (args.length > 0)
     {
-      testUrls = new ArrayList<>();
+      instance.testUrls = new ArrayList<>();
       for (String url : args)
       {
-        testUrls.add(url);
+        instance.testUrls.add(url);
       }
     }
-    Thread runner = getDiscoverer()
-            .startDiscoverer(new PropertyChangeListener()
-            {
+    Thread runner = instance.startDiscoverer(new PropertyChangeListener()
+    {
 
-              @Override
-              public void propertyChange(PropertyChangeEvent evt)
-              {
-                if (getDiscoverer().services != null)
-                {
-                  System.out.println("Changesupport: There are now "
-                          + getDiscoverer().services.size() + " services");
-                  int i = 1;
-                  for (Jws2Instance instance : getDiscoverer().services)
-                  {
-                    System.out.println("Service " + i++ + " "
-                            + instance.getClass() + "@" + instance.getHost()
-                            + ": " + instance.getActionText());
-                  }
+      @Override
+      public void propertyChange(PropertyChangeEvent evt)
+      {
+        if (getInstance().services != null)
+        {
+          System.out.println("Changesupport: There are now "
+                  + getInstance().services.size() + " services");
+          int i = 1;
+          for (Jws2Instance instance : getInstance().services)
+          {
+            System.out.println("Service " + i++ + " " + instance.getClass()
+                    + "@" + instance.getHost() + ": "
+                    + instance.getActionText());
+          }
 
-                }
-              }
-            });
+        }
+      }
+    });
     while (runner.isAlive())
     {
       try
@@ -630,20 +641,6 @@ public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
     }
   }
 
-  /**
-   * Returns the singleton instance of this class.
-   * 
-   * @return
-   */
-  public static Jws2Discoverer getDiscoverer()
-  {
-    if (discoverer == null)
-    {
-      discoverer = new Jws2Discoverer();
-    }
-    return discoverer;
-  }
-
   public boolean hasServices()
   {
     return !running && services != null && services.size() > 0;
index 23c6949..319252a 100644 (file)
@@ -107,7 +107,7 @@ public class MsaWSClient extends Jws2Client
     if (!(sh.service instanceof MsaWS))
     {
       // redundant at mo - but may change
-      JvOptionPane.showMessageDialog(Desktop.desktop,
+      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
               MessageManager.formatMessage(
                       "label.service_called_is_not_msa_service",
                       new String[]
@@ -120,7 +120,7 @@ public class MsaWSClient extends Jws2Client
     server = (MsaWS) sh.service;
     if ((wsInfo = setWebService(sh, false)) == null)
     {
-      JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager
+      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), MessageManager
               .formatMessage("label.msa_service_is_unknown", new String[]
               { sh.serviceType }),
               MessageManager.getString("label.internal_jalview_error"),
index 45bddac..67e3338 100644 (file)
@@ -242,7 +242,7 @@ public class SequenceAnnotationWSClient extends Jws2Client
           @Override
           public void actionPerformed(ActionEvent arg0)
           {
-            Desktop.instance.showUrl(service.docUrl);
+            Desktop.getInstance().showUrl(service.docUrl);
           }
         });
         annotservice.setToolTipText(
index e092192..7954db0 100644 (file)
@@ -186,7 +186,7 @@ public class Jws2Instance implements AutoCloseable
       try
       {
         paramStore = new JabaParamStore(this,
-                (Desktop.instance != null ? Desktop.getUserParameterStore()
+                (Desktop.getInstance() != null ? Desktop.getUserParameterStore()
                         : null));
       } catch (Exception ex)
       {
index 623b8de..1983ff5 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.ws.jws2.jabaws2;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.ws.jws2.AAConClient;
 import jalview.ws.jws2.RNAalifoldClient;
 import jalview.ws.uimodel.AlignAnalysisUIText;
@@ -29,11 +31,23 @@ import java.util.HashSet;
 
 import compbio.data.msa.JABAService;
 
-public class Jws2InstanceFactory
+public class Jws2InstanceFactory implements ApplicationSingletonI
 {
-  private static HashMap<String, AlignAnalysisUIText> aaConGUI;
 
-  private static HashSet<String> ignoreGUI;
+  private Jws2InstanceFactory()
+  {
+    // private singleton
+  }
+
+  private static Jws2InstanceFactory getInstance()
+  {
+    return (Jws2InstanceFactory) ApplicationSingletonProvider
+            .getInstance(Jws2InstanceFactory.class);
+  }
+
+  private HashMap<String, AlignAnalysisUIText> aaConGUI;
+
+  private HashSet<String> ignoreGUI;
 
   private static String category_rewrite(String cat_name)
   {
@@ -42,17 +56,17 @@ public class Jws2InstanceFactory
             : cat_name;
   }
 
-  private static void init()
+  private void init()
   {
     if (aaConGUI == null)
     {
-      aaConGUI = new HashMap<String, AlignAnalysisUIText>();
+      aaConGUI = new HashMap<>();
       aaConGUI.put(compbio.ws.client.Services.AAConWS.toString(),
               AAConClient.getAlignAnalysisUITest());
       aaConGUI.put(compbio.ws.client.Services.RNAalifoldWS.toString(),
               RNAalifoldClient.getAlignAnalysisUITest());
       // ignore list for JABAWS services not supported in jalview ...
-      ignoreGUI = new HashSet<String>();
+      ignoreGUI = new HashSet<>();
     }
   }
 
@@ -65,8 +79,8 @@ public class Jws2InstanceFactory
    */
   public static boolean ignoreService(String serviceType)
   {
-    init();
-    return (ignoreGUI.contains(serviceType.toString()));
+    getInstance().init();
+    return (getInstance().ignoreGUI.contains(serviceType.toString()));
   }
 
   /**
@@ -84,10 +98,10 @@ public class Jws2InstanceFactory
           String serviceType, String name, String description,
           JABAService service)
   {
-    init();
+    getInstance().init();
     Jws2Instance svc = new Jws2Instance(jwsservers, serviceType,
             category_rewrite(name), description, service);
-    svc.aaui = aaConGUI.get(serviceType.toString());
+    svc.aaui = getInstance().aaConGUI.get(serviceType.toString());
     return svc;
   }
 
index a71b70d..5d1a288 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.ws.rest;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentView;
 import jalview.gui.AlignFrame;
@@ -49,8 +51,27 @@ import javax.swing.event.MenuListener;
  * 
  */
 public class RestClient extends WSClient
-        implements WSClientI, WSMenuEntryProviderI
+implements WSClientI, WSMenuEntryProviderI, ApplicationSingletonI
 {
+
+  @SuppressWarnings("unused")
+  private RestClient()
+  {
+    // accessed by ApplicationSingletonProvider
+  }
+
+  
+private static RestClient getInstance()
+{
+return (RestClient) ApplicationSingletonProvider.getInstance(RestClient.class);
+}
+
+public static final String RSBS_SERVICES = "RSBS_SERVICES";
+
+
+  protected Vector<String> services = null;
+
+
   RestServiceDescription service;
 
   public RestClient(RestServiceDescription rsd)
@@ -109,7 +130,7 @@ public class RestClient extends WSClient
     if (!headless)
     {
       wsInfo = new WebserviceInfo(WebServiceJobTitle,
-              WebServiceName + "\n" + WebServiceReference, true);
+              WebServiceName + "\n" + WebServiceReference, Desktop.FRAME_MAKE_VISIBLE);
       wsInfo.setRenderAsHtml(true);
     }
 
@@ -329,7 +350,7 @@ public class RestClient extends WSClient
     else
     {
       // TODO: try to tell the user why the job couldn't be started.
-      JvOptionPane.showMessageDialog(Desktop.desktop,
+      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
               (jobsthread.hasWarnings() ? jobsthread.getWarnings()
                       : MessageManager.getString(
                               "label.job_couldnt_be_started_check_input")),
@@ -399,12 +420,13 @@ public class RestClient extends WSClient
     return true;
   }
 
-  protected static Vector<String> services = null;
-
-  public static final String RSBS_SERVICES = "RSBS_SERVICES";
-
   public static RestClient[] getRestClients()
   {
+    return getInstance().getClients();
+  }
+    
+  private RestClient[] getClients()
+  {
     if (services == null)
     {
       services = new Vector<String>();
@@ -458,10 +480,11 @@ public class RestClient extends WSClient
   {
     if (rsbsUrls != null)
     {
+      
       // TODO: consider validating services ?
-      services = new Vector<String>(rsbsUrls);
+      getInstance().services = new Vector<String>(rsbsUrls);
       StringBuffer sprop = new StringBuffer();
-      for (String s : services)
+      for (String s : getInstance().services)
       {
         sprop.append(s);
       }
index 4fb9ca9..3ec6320 100644 (file)
@@ -22,20 +22,15 @@ package jalview.ws.sifts;
 
 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;
@@ -101,8 +96,6 @@ public class SiftsClient implements SiftsClientI
    */
   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;
@@ -117,10 +110,15 @@ public class SiftsClient implements SiftsClientI
 
   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");
@@ -165,8 +163,31 @@ public class SiftsClient implements SiftsClientI
   {
     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());
+    }
   }
 
   /**
@@ -180,8 +201,17 @@ public class SiftsClient implements SiftsClientI
    */
   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");
@@ -190,10 +220,6 @@ public class SiftsClient implements SiftsClientI
       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());
     }
   }
 
@@ -223,14 +249,14 @@ public class SiftsClient implements SiftsClientI
       // The line below is required for unit testing... don't comment it out!!!
       System.out.println(">>> SIFTS File already downloaded for " + pdbId);
 
-      if (isFileOlderThanThreshold(siftsFile,
+      if (Platform.isFileOlderThanThreshold(siftsFile,
               SiftsSettings.getCacheThresholdInDays()))
       {
         File oldSiftsFile = new File(siftsFileName + "_old");
         siftsFile.renameTo(oldSiftsFile);
         try
         {
-          siftsFile = downloadSiftsFile(pdbId.toLowerCase());
+          siftsFile = downloadSiftsFile(pdbId);
           oldSiftsFile.delete();
           return siftsFile;
         } catch (IOException e)
@@ -247,7 +273,7 @@ public class SiftsClient implements SiftsClientI
     }
     try
     {
-      siftsFile = downloadSiftsFile(pdbId.toLowerCase());
+      siftsFile = downloadSiftsFile(pdbId);
     } catch (IOException e)
     {
       throw new SiftsException(e.getMessage());
@@ -256,35 +282,6 @@ public class SiftsClient implements SiftsClientI
   }
 
   /**
-   * 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
@@ -294,53 +291,47 @@ public class SiftsClient implements SiftsClientI
    */
   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();
     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;
   }
 
@@ -648,7 +639,7 @@ public class SiftsClient implements SiftsClientI
       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();
@@ -660,7 +651,7 @@ public class SiftsClient implements SiftsClientI
             pdbRefDb = cRefDb;
             if (firstPDBResNum == UNASSIGNED)
             {
-              firstPDBResNum = getLeadingIntegerValue(cRefDb.getDbResNum(),
+              firstPDBResNum = Platform.getLeadingIntegerValue(cRefDb.getDbResNum(),
                       UNASSIGNED);
             }
             else
@@ -675,7 +666,7 @@ public class SiftsClient implements SiftsClientI
           if (cRefDb.getDbCoordSys().equalsIgnoreCase(seqCoordSys.getName())
                   && isAccessionMatched(cRefDb.getDbAccessionId()))
           {
-            currSeqIndex = getLeadingIntegerValue(cRefDb.getDbResNum(),
+            currSeqIndex = Platform.getLeadingIntegerValue(cRefDb.getDbResNum(),
                     UNASSIGNED);
             if (pdbRefDb != null)
             {
@@ -725,9 +716,9 @@ public class SiftsClient implements SiftsClientI
         {
 
           int resNum = (pdbRefDb == null)
-                  ? getLeadingIntegerValue(residue.getDbResNum(),
+                  ? Platform.getLeadingIntegerValue(residue.getDbResNum(),
                           UNASSIGNED)
-                  : getLeadingIntegerValue(pdbRefDb.getDbResNum(),
+                  : Platform.getLeadingIntegerValue(pdbRefDb.getDbResNum(),
                           UNASSIGNED);
 
           if (isObserved)
@@ -748,29 +739,6 @@ public class SiftsClient implements SiftsClientI
   }
 
   /**
-   * 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.
index 5e2c526..029f48d 100644 (file)
  */
 package jalview.ws.sifts;
 
+import jalview.bin.ApplicationSingletonProvider;
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
+import jalview.util.Platform;
+
 import java.util.Objects;
 
-public class SiftsSettings
+public class SiftsSettings implements ApplicationSingletonI
 {
-  private static boolean mapWithSifts = false;
+  /**
+   * Constructor
+   * 
+   * @return
+   */
+  private static SiftsSettings getInstance()
+  {
+    return (SiftsSettings) ApplicationSingletonProvider
+            .getInstance(SiftsSettings.class);
+  }
+
+  private SiftsSettings()
+  {
+    // singleton; use getInstance()
+  }
+
+  private boolean mapWithSifts = false;
 
-  private static String siftDownloadDirectory;
+  private String siftDownloadDirectory;
 
-  private static int cacheThresholdInDays;
+  private int cacheThresholdInDays;
 
-  private static int failSafePIDThreshold;
+  private int failSafePIDThreshold;
 
   public static boolean isMapWithSifts()
   {
-    return mapWithSifts;
+    return getInstance().mapWithSifts;
   }
 
   public static void setMapWithSifts(boolean mapWithSifts)
   {
-    SiftsSettings.mapWithSifts = mapWithSifts;
+    getInstance().mapWithSifts = mapWithSifts;
   }
 
   public static String getSiftDownloadDirectory()
   {
-    return siftDownloadDirectory;
+    return getInstance().siftDownloadDirectory;
   }
 
   public static void setSiftDownloadDirectory(String siftDownloadDirectory)
   {
-    SiftsSettings.siftDownloadDirectory = siftDownloadDirectory;
+    getInstance().siftDownloadDirectory = siftDownloadDirectory;
   }
 
   public static int getCacheThresholdInDays()
   {
-    return cacheThresholdInDays;
+    return getInstance().cacheThresholdInDays;
   }
 
   public static void setCacheThresholdInDays(String cacheThresholdInDays)
   {
     Objects.requireNonNull(cacheThresholdInDays);
-    SiftsSettings.cacheThresholdInDays = Integer
+    getInstance().cacheThresholdInDays = Integer
             .valueOf(cacheThresholdInDays);
   }
 
   public static int getFailSafePIDThreshold()
   {
-    return failSafePIDThreshold;
+    return getInstance().failSafePIDThreshold;
   }
 
   public static void setFailSafePIDThreshold(String failSafePIDThreshold)
   {
     Objects.requireNonNull(failSafePIDThreshold);
-    SiftsSettings.failSafePIDThreshold = Integer
+    getInstance().failSafePIDThreshold = Integer
             .valueOf(failSafePIDThreshold);
   }
 }
index 58632f2..227fe23 100644 (file)
@@ -53,64 +53,7 @@ public class UrlDownloadClient
   public static void download(String urlstring, String outfile)
           throws IOException
   {
-
-      FileOutputStream fos = null;
-      ReadableByteChannel rbc = null;
-      Path temp = null;
-      try
-      {
-        temp = Files.createTempFile(".jalview_", ".tmp");
-
-        URL url = new URL(urlstring);
-        rbc = Channels.newChannel(url.openStream());
-        fos = new FileOutputStream(temp.toString());
-        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 (fos != null)
-          {
-            fos.close();
-          }
-        } catch (IOException e)
-        {
-          System.out.println(
-                  "Exception while closing download file output stream: "
-                          + e.getMessage());
-        }
-        try
-        {
-          if (rbc != null)
-          {
-            rbc.close();
-          }
-        } catch (IOException e)
-        {
-          System.out.println("Exception while closing download channel: "
-                  + e.getMessage());
-        }
-        try
-        {
-          if (temp != null)
-          {
-            Files.deleteIfExists(temp);
-          }
-        } catch (IOException e)
-        {
-          System.out.println("Exception while deleting download temp file: "
-                  + e.getMessage());
-        }
-      }
-
+      Platform.download(urlstring, outfile);
   }
 
   public static void download(String urlstring, File tempFile) throws IOException
index b57528b..3650544 100644 (file)
@@ -1,5 +1,6 @@
 package javajs.async;
 
+import java.awt.Toolkit;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -361,7 +362,7 @@ public class Assets {
                try {
                        URL url = getInstance()._getURLFromPath(path, true);
                        if (url == null && !zipOnly) {
-                               url = Assets.class.getResource(path);
+                               url = Assets.class.getClassLoader().getResource(path);
                        }
                        if (url != null)
                                return url.openStream();
index df103cd..703f7f5 100644 (file)
@@ -10,6 +10,8 @@ import javajs.async.SwingJSUtils.StateHelper;
 import javajs.async.SwingJSUtils.StateMachine;
 
 /**
+ * v. 2020.06.03 
+ * 
  * Executes synchronous or asynchronous tasks using a SwingWorker in Java or
  * JavaScript, equivalently.
  * 
@@ -38,18 +40,56 @@ import javajs.async.SwingJSUtils.StateMachine;
  * the subclass to update the progress field in both the SwingWorker and the
  * ProgressMonitor.
  * 
- * If it is desired to run the AsyncSwingWorker synchonously, call the
+ * If it is desired to run the AsyncSwingWorker synchronously, call the
  * executeSynchronously() method rather than execute(). Never call
  * SwingWorker.run().
  * 
+ * Note that doInBackgroundAsync runs on the Java AWT event queue. This means
+ * that, unlike a true SwingWorker, it will run in event-queue sequence, after
+ * anything that that method itself adds to the queue. This is what SwingWorker itself
+ * does with its done() signal. 
+ * 
+ * If doInBackgroundAsync has tasks that are time intensive, the thing to do is to
+ * 
+ * (a) pause this worker by setting the value of progress for the NEXT step:
+ * 
+ *    setProgressAsync(n);
+ *    
+ * (b) pause the timer so that when doInBackgroundAsync returns, the timer is not fired:
+ * 
+ *    setPaused(true);
+ * 
+ * (c) start your process as new Thread, which bypasses the AWT EventQueue:
+ * 
+ *    new Thread(Runnable).start();
+ *    
+ * (d) have your thread, when it is done, return control to this worker:
+ * 
+ *    setPaused(false);
+ *    
+ * This final call restarts the worker with the currently specified progress value.
  * 
  * @author hansonr
  *
  */
 public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implements StateMachine {
 
+
+       // PropertyChangeEvent getPropertyName()
+       
+       private static final String PROPERTY_STATE = "state";
+       private static final String PROPERTY_PAUSE = "pause";
+       
+       // PropertyChangeEvent getNewValue()
+       
+       public static final String STARTED_ASYNC = "STARTED_ASYNC";
+       public static final String STARTED_SYNC = "STARTED_SYNC";
+       
        public static final String DONE_ASYNC = "DONE_ASYNC";
        public static final String CANCELED_ASYNC = "CANCELED_ASYNC";
+       
+       public static final String PAUSED = "PAUSED";
+       public static final String RESUMED = "RESUMED";
 
        protected int progressAsync;
 
@@ -119,10 +159,12 @@ public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implement
        }
 
        public void executeAsync() {
+               firePropertyChange(PROPERTY_STATE, null, STARTED_ASYNC);
                super.execute();
        }
 
        public void executeSynchronously() {
+               firePropertyChange(PROPERTY_STATE, null, STARTED_SYNC);
                isAsync = false;
                delayMillis = 0;
                try {
@@ -244,7 +286,7 @@ public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implement
        private final static int STATE_LOOP = 1;
        private final static int STATE_WAIT = 2;
        private final static int STATE_DONE = 99;
-
+       
        private StateHelper helper;
 
        protected StateHelper getHelper() {
@@ -255,6 +297,9 @@ public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implement
 
        protected void setPaused(boolean tf) {
                isPaused = tf;
+               firePropertyChange(PROPERTY_PAUSE, null, (tf ? PAUSED : RESUMED));
+               if (!tf)
+                       stateLoop();
        }
 
        protected boolean isPaused() {
@@ -292,7 +337,7 @@ public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implement
                        case STATE_LOOP:
                                if (checkCanceled()) {
                                        helper.setState(STATE_DONE);
-                                       firePropertyChange("state", null, CANCELED_ASYNC);
+                                       firePropertyChange(PROPERTY_STATE, null, CANCELED_ASYNC);
                                } else {
                                        int ret = doInBackgroundAsync(progressAsync);                                   
                                        if (!helper.isAlive() || isPaused) {
@@ -309,6 +354,7 @@ public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implement
                                }
                                continue;
                        case STATE_WAIT:
+                               // meaning "sleep" and then "loop"
                                helper.setState(STATE_LOOP);
                                helper.sleep(delayMillis);
                                return true;
@@ -343,7 +389,7 @@ public abstract class AsyncSwingWorker extends SwingWorker<Void, Void> implement
                @Override
                public void run() {
                        doneAsync();
-                       firePropertyChange("state", null, DONE_ASYNC);
+                       firePropertyChange(PROPERTY_STATE, null, DONE_ASYNC);
                }
 
        };
index 1b6ff9b..322ec83 100644 (file)
@@ -7,6 +7,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URL;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
 import java.util.function.Function;
 import java.util.zip.ZipEntry;
@@ -91,10 +92,10 @@ public interface JSUtilI {
 
 
        /**
-        * Get an attribute of applet's Info map for the applet found using
-        * getApplet(null). That is, applet.__Info[InfoKey].
+        * Get the applet's __Info map or an attribute of that map for the applet found using
+        * getApplet(null). That is, applet.__Info or applet.__Info[InfoKey].
         * 
-        * @param infoKey
+        * @param infoKey if null, return the full __Info map
         */
        Object getAppletInfo(String infoKey);
 
@@ -329,4 +330,25 @@ public interface JSUtilI {
         */
        void setUIEnabled(JComponent jc, boolean enabled);
 
+
+       /**
+        * Play an audio
+        * @param buffer
+        * @param format a javax.sound.sampled.AudioFormat
+        * @throws Exception 
+        */
+       void playAudio(byte[] buffer, Object format) throws Exception;
+
+       /**
+        * For either an applet or an application, get the ORIGINAL __Info as a Map that
+        * has a full set up lower-case keys along with whatever non-all-lower-case keys
+        * provided at start-up.
+        * 
+        * @return
+        */
+       Map<String, Object> getAppletInfoAsMap();
+
+       
+  void setAppClass(Object j);
+
 }
index d5e28c1..5e9828e 100644 (file)
Binary files a/swingjs/SwingJS-site.zip and b/swingjs/SwingJS-site.zip differ
diff --git a/swingjs/differences.txt b/swingjs/differences.txt
new file mode 100644 (file)
index 0000000..70eabbc
--- /dev/null
@@ -0,0 +1,1480 @@
+Notes
+=====
+
+---IMPORTANT CHARACTER SET NOTE---
+
+It is critical that all development work in Java2Script 
+be done in UTF-8. This means:
+
+- making sure your Eclipse project is set up for UTF-8 (not the Eclipse default?)
+- making sure your server can serve up UTF-8 by default for any browser-loaded files
+- making sure you don't edit a Java2Script class file or one of the site .js files
+    using a non-UTF-8 editor. It may replace non-Latin characters with "?" or garbage.
+- making sure that your web pages are delivered with proper headings indicating HTML5 and UTF-8
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+
+Note that the DOCTYPE tag is critical for some browsers to switch into HTML5 mode. (MSIE?)
+
+
+
+  
+In particular, the Mandarin character ç§˜ (mi; "secret") is used extensively throughout
+the SwingJS class files to distinguish j2s-specific fields and methods that must not 
+ever be shadowed or overridden by subclasses. For example, we see in java.lang.Thread.java:
+
+               public static JSThread ç§˜thisThread;
+
+----------------------------------
+
+
+updated 3/21/2020 -- adds note about HashMap, Hashtable, and HashSet iterator ordering
+updated 3/20/2020 -- adds note about interning, new String("xxx"), and "xxx"
+updated 2/26/2020 -- adds Graphics.setClip issue
+updated 12/22/19 -- additional issues
+updated 11/03/19 -- adds information about File.exists() and points to src/javajs/async
+updated 10/26/19 -- adds information about File.createTempFile()
+updated 8/16/19 -- minor typos and added summary paragraph
+updated 7/19/19 -- clarification that AWT and Swing classes are supported directly
+updated 5/13/19 -- Mandarin U+79D8 reserved character; Missing Math methods; int and long
+updated 5/10/19 -- adds a section on static issues in multi-(duplicate)-applet pages
+updated 1/4/19 -- nio
+updated 9/15/18 -- adds integer 1/0 == Infinity
+updated 7/24/18 -- most classes replaced with https://github.com/frohoff/jdk8u-jdk
+updated 6/5/17 -- reserved package name "window"
+updated 3/11/17 -- myClass.getField
+updated 3/7/17 -- overloading of JSplitPane.setDividerLocation
+updated 3/2/17 -- more indication of classes not implemented (KeyListener)
+
+=============================================================================
+SwingJS and OpenJDK 8+
+=============================================================================
+
+SwingJS implements a wide range of the Java language in JavaScript. The base
+version for this implementation is OpenJDK8. some classes are implemented using 
+older source code, and there are some missing methods. For the most part, this is 
+no real problem. You can add or modify any java class just be adding it as source
+in your project. Or (preferably) you can contact me, and I can get it into the 
+distribution. Or (even more preferably) you can do that via a patch submission. 
+
+=================
+DESIGN PHILOSOPHY
+=================
+
+The java2script/SwingJS design goal is to recreate a recognizable, easily debuggable
+equivalent in JavaScript for as much of Java as practical. This means, for example, 
+that one can call in JavaScript 
+
+  new java.util.Hashtable()
+  
+and for all practical purposes it will appear that Java is running.
+
+
+Method and Field Disambiguation
+-------------------------------
+
+SwingJS has no problem with the overloading of methods, for example:
+
+  public void print(int b);
+  public void print(float b);
+
+JavaScript does not allow overloading of methods, and the common practice in
+Java of naming a field the same as a method -- isAllowed and isAllowed() -- is
+not possible in JavaScript. As a result, SwingJS implements "fully-qualified" 
+method names using "$" parameter type separation. Thus, these methods in SwingJS
+will be referred to as print$I and print$F. The rules for this encoding are
+relatively simple: 
+
+1. The seven primitive types in Java are encoded $I (int), $L (long), $F (float), 
+$D (double), $B (byte) $Z (boolean), and $H (short). 
+
+2. String and Object are encoded as $S and $O, respectively.
+
+3. "java_lang_" is dropped for all other classes in the java.lang package (as in Java).
+   For example:  $StringBuffer, not $java_lang_StringBuffer
+
+4. All other classes are encoded as 
+
+ "$" + Class.getName().replace(".","_")
+
+For example, in Java we see:
+
+  public void equals(Object o) {...}
+
+Whereas in SwingJS we have:
+
+  Clazz.newMeth(C$, 'equals$O', function (o) {...}
+
+And 
+
+ this.getContentPane().add(bar, "North");
+
+becomes
+
+ this.getContentPane$().add$java_awt_Component$O(bar, "North");
+
+5. Arrays are indicated with appended "A" for each level. So
+
+  setDataVector(Object[][] dataVector, Object[] columnIdentifiers)
+  
+becomes
+
+  setDataVector$OAA$OA(dataVector, columnIdentifiers)
+
+(It is recognized that this design does introduce a bit of ambiguity, in that
+ in principal there could be user class named XA and X in the same package,
+ and methods a(X[]) and a(XA) in the same class that cannot be distinguished.
+ The benefit of this simple system, however, triumphed over the unlikelyhood
+ of that scenario.) The transpiler could be set to flag this possibility.
+
+6. Constructors are prepended with "c$". So 
+
+  public JLabel(String text) {...}
+  
+becomes:
+
+  Clazz.newMeth(C$, 'c$$S', function (text) {...});
+
+Field disambiguation involves prepending. In Java, a class and its subclass 
+can both have the same field name, such as 
+
+ boolean visible;
+When this happens, it is called "shadowing", and though not recommended, Java allows
+it. The Java2Script transpiler will prepend such shadowing fields with "$" so that the
+subclass instance has both "visible" (for use in its methods inherited from its
+superclass) and "$visible" (for its own methods). Thus, we might see in Java:
+
+  this.visible = super.visible;
+  
+while in SwingJS we will see:
+
+  this.$visible=this.visible;
+
+since JavaScript does not have the "super" keyword.
+
+
+
+Parameterless methods such as toString() are appended with "$" to become toString$().
+The one exception to this rule is private methods, which are saved in (truly) private 
+array in the class (and are not accessible by reflection). Private parameterless 
+methods retain their simple Java name, since they cannot conflict with field names.
+
+This renaming of methods has a few consequences, which are discussed more fully below.
+See particularly the section on "qualified field and method names", where it is described
+how you can use packages or classes or interfaces with ".api.js" in them to represent JavaScript
+objects for which all method names are to be left unqualified. Note that it is not 
+possible to cherry-pick methods to be unqualified; only full packages, classes or 
+interfaces can hold this status.
+
+The swingjs.api.js package in particular contains a number of useful interfaces that
+you can import into your project for JavaScript-specific capabilities.
+
+
+Applet vs. Application
+----------------------
+
+One of the very cool aspects of SwingJS is that it doesn't particularly matter if a browser-based
+Java app is an "applet" or an "application". We don't need JNLP (Java Network Launch Protocol) 
+because now we can just start up any Java application in a browser just as easily as any applet.
+The associative array that passes information to the SwingJS applet (information that formerly
+might have been part of the APPLET tag, such as width, height, and codebase, always referred to 
+in our writing as "the Info array") allows the option to specify the JApplet/Applet "code" 
+class or the application "main" class. Either one will run just fine.
+
+
+Performance
+-----------
+
+Obviously, there are limitations. One is performance, but we have seen reproducible 
+performance at 1/6 - 1/3 the speed of Java. Achieving this performance may require
+some refactoring of the Java to make it more efficient in both Java and JavaScript. 
+"for" loops need to be more carefully crafted; use of "new" and "instanceof" need to be
+minimized in critical areas. Note that method overloading -- that is, the same method name
+with different parameters, such as read(int) and read(byte) -- is no longer any problem. 
+  
+
+Threads
+-------
+
+Although there is only a single thread in JavaScript, meaning Thread.wait(), Thread.sleep(int) and 
+Thread.notify() cannot be reproduced, we have found that this is not a serious limitation. 
+For example, javax.swing.Timer() works perfectly in JavaScript. All it means is that threads 
+that use sleep(int) or notify() must be refactored to allow Timer-like callbacks. That is, 
+they must allow full exit and re-entry of Thread.run(), not the typical while/sleep motif. 
+
+The key is to create a state-based run() that can be exited and re-entered in JavaScript.
+
+
+Static fields
+-------------
+
+Final static primitive "constant" fields (String, boolean, int, etc.) such as 
+
+static final int TEST = 3;
+static final String MY_STRING = "my " + "string";
+
+are converted to their primitive form automatically by the Eclipse Java compiler 
+and do not appear in the JavaScript by their names. 
+
+Other static fields are properties of their class and can be used as expected.
+
+Note, however, that SwingJS runs all "Java" code on a page in a common "jvm" 
+(like older versions of Java). So, like the older Java schema, the JavaScript 
+equivalents of both applets and applications will share all of their static 
+fields and methods. This includes java.lang.System. 
+
+Basically, SwingJS implementations of Java run in a browser page-based sandbox 
+instead of an applet-specific one.
+
+In general, this is no problem. But if we are to implement pages with 
+multiple applets present, we must be sure to only have static references 
+that are "final" or specifically meant to be shared in a JavaScript 
+environment only (since they will not be shared in Java).
+
+A simple solution, if static non-constant references are needed, is to attach the 
+field to Thread.currentThread.threadGroup(), which is an applet-specific reference.
+Be sure, if you do this, that you use explicit setters and getters:
+
+For example, 
+
+private static String myvar;
+
+...
+
+public void setMyVar(String x) {
+  ThreadGroup g = Thread.currentThread().threadGroup();
+  /**
+   * @j2sNative g._myvar = x;
+   * 
+   */
+   {
+     myvar = x;
+   }
+}
+
+public String getMyVar() {
+  ThreadGroup g = Thread.currentThread().threadGroup();
+  /**
+   * @j2sNative return g._myvar || null;
+   * 
+   */
+   {
+     return myvar;
+   }
+}
+ in Java will get and set x the same in JavaScript and in Java. 
+A convenient way to do this in general is to supply a singleton class with
+explicitly private-only constructors and then refer to it in Java and in JavaScript
+instead of using static field, referring to myclass.getIntance().xxx instead of 
+myclass.xxx in Java (and JavaScript). 
+
+This was done extensively in the Jalview project. See jalview.bin.Instance.
+
+
+Helper Packages -- swingjs/ and javajs/
+---------------------------------------
+
+The SwingJS library is the swingjs/ package. There are interfaces that may be of assistance
+in swingjs/api, but other than that, it is not recommended that developers access classes in 
+this package. The "public" nature of their methods is really an internal necessity.
+
+In addition to swingjs/, though, there are several useful classes in the javajs/ package
+that could be very useful. This package is a stand-alone package that can be 
+cloned in any Java project that also would be great to have in any JavaScript project
+-- SwingJS-related or not. Functionality ranges from reading and writing various file 
+formats, including PDF, BMP, PNG, GIF, JPG, JSON, ZIP, and CompoundDocument formats.
+
+A variety of highly efficient three- and four-dimensional point, vector, matrix, and 
+quaternion classes are included, as they were developed for JSmol and inherited from that
+project. 
+
+Of particular interest should be javajs/async/, which includes
+
+javajs.async.Async
+javajs.async.AsyncColorChooser
+javajs.async.AsyncDialog
+javajs.async.AsyncFileChooser
+
+See javajs.async.Async JavaDoc comments for a full description of 
+these useful classes.
+
+
+Modal Dialogs
+-------------
+
+Although true modal dialogs are not possible with only one thread, a functional equivalent -- 
+asynchronous modal dialogs -- is relatively easy to set up. All the JOptionPane dialogs will
+return PropertyChangeEvents to signal that they have been disposed of and containing the results. 
+See below and classes in the javajs.async package.
+
+
+Native calls
+------------
+
+Native calls in Java are calls to operating system methods that are not in Java. JavaScript
+has no access to these, of course, and they must all be replaced by JavaScript equivalents.
+Fortunately, they are not common, and those that are present in Java (for example, in calculating
+checksums in ZIP file creation) are at a low enough level that most developers do not utilize them
+or do not even have access to them. All native calls in Java classes have been replaced by 
+Java equivalents.
+
+
+Swing GUI Peers and UIClasses
+-----------------------------
+
+One of the biggest adaptations introduced in SwingJS is in the area of the graphical 
+user interface. The issue here is complex but workable. In Java there are two background 
+concepts -- the Component "peer" (one per "heavy-weight" component, such as a Frame) and the 
+component "uiClass" (one per component, such as JButton or JTextField).
+
+Peers are native objects of the operating system. These are the virtual buttons and text areas
+that the user is interacting with at a very base level. Their events are being passed on to 
+Java or the browser by the operating system. UI classes provide a consistent "look and feel" 
+for these native objects, rendering them onto the native window canvas and handling all 
+user-generated events. They paint the borders, the backgrounds, the highlights, of every 
+control you see in Java. There is one-to-one correspondence of Swing classes and UI classes. 
+Setting the Look and Feel for a project amounts to selecting the directory from which to draw 
+these UI classes. The UI classes can be found in the javax.swing.plaf ("platform look and feel") 
+package.
+
+Early on in the development of SwingJS, we decided not to fully reproduce the painfully detailed 
+bit-by-bit painting of controls as is done in Java. Instead, we felt it was wiser to utilize the standard
+HTML5 UI capabilities as much as possible, using DIV, and INPUT especially, with extensive use
+of CSS and sometimes jQuery (menus, and sliders, for example). Thus, we have created a new 
+set of UIs -- the "HTML5 Look and Feel". These classes can be found in swingjs.plaf. Besides being
+more adaptable, this approach allows far more versatility to SwingJS developers, allowing them
+to modify the GUI to suit their needs if desired.
+
+In SwingJS, since we have no access to native peers except through the browser DOM,
+it seemed logical to merge the peer and UI idea. So instead of having one peer per heavy-weight control and
+one UI class instance for each control type, we just have one UI class instance per control, and
+that UI class instance is what is being referred to when a "peer" is notified. 
+
+In some ways this is a throw back to when all of Swing's components were subclasses of
+specific AWT components such as Button and List. These "heavy-weight components" all had their 
+own individual native peers and thus automatically took on the look and feel provided by the OS. 
+Later Swing versions implemented full look and feel for all peers, leaving only JDialog, JFrame,
+and a few other classes to have native peers. But in SwingJS we have again a 1:1 map of component
+and UI class/peer instance.
+
+The origin of most issues (read "bugs") in relation to the GUI will probably be found in the
+swingjs.plaf JSxxxxUI.java code.
+
+  
+Swing-only Components -- no longer an issue
+-------------------------------------------
+
+Swing was introduced into Java well after the Java Abstract Window Toolkit (AWT) was well
+established. As such, its designers chose to allow AWT controls such as Button and List to be used 
+alongside their Swing counterparts JButton and JList. Reading the code, it is clear that this 
+design choice posed a huge headache for Swing class developers. 
+
+For SwingJS, we decided from the beginning NOT to allow this mixed-mode programming and 
+instead to require that all components be Swing components. 
+
+However, this is no longer an issue. All AWT components in SwingJS are now subclasses of 
+javax.swing.JComponent. So far, we have found no problem with this.
+
+The a2s Adapter Package
+-----------------------
+
+Originally, we thought that we would restrict ourselves to JApplets only. That is, only
+Swing-based applets. But as we worked, we discovered that there are a lot of great 
+applets out there that are pre-Swing pure-AWT java.applet.Applet applets. Our problem was 
+that we also wanted it to be possible to quickly adapt these applets to JavaScript as well.
+The solution turned out to be simple: Write a package (a2s) that recreates the interface for 
+non-Swing components as subclasses of Swing components. Thus, a2s.Button subclasses javax.swing.JButton
+but also accepts all of the methods of java.awt.Button. This works amazingly well, with a few
+special adaptations to the core javax.swing to be "AWT-aware." All AWT components now subclass 
+a2s components, which in turn subclass JComponents. So no changes in code are necessary. We have
+successfully transpiled over 500 applets using this strategy. (Kind of surprising, actually, that
+the original Java developers did not see that option. But we have a hindsight advantage here.)
+
+
+Working with Files
+==================
+
+Simple String file names are not optimal for passing information about
+read files within SwingJS applications. 
+All work with files should either use Path or File objects exclusively. 
+These objects, after a file is read or checked for existence, will already 
+contain the file byte[] data. Doing something like this:
+
+File f = File("./test.dat");
+boolean isOK = f.exists();
+
+will load f with its byte[] data, if the file exists. 
+
+But if after that, we use:
+
+File f2 = new File(f.getAbsolutePath());
+
+f2 will not contain that data. Such copying should be done as:
+
+File f2 = new File(f);
+
+in which case, the byte[] data will be transferred.
+
+
+SwingJS uses the following criteria to determine if File.exists() returns true:
+
+(1) if this File object has been used directly to read data, or 
+(2) if reading data using this File object is successful.
+
+Note that you cannot check to see if a file exists before input or if it 
+was actually written or if it already exists prior to writing in SwingJS.  
+
+Thus, you should check each use of file.exists() carefully, and if necessary, provide a J2sNative 
+block that gives an appropriate "OK" message, for example:
+
+(/** @j2sNative 1 ? false : */ outputfile.exits())
+
+or 
+
+(/** @j2sNative 1 ? true : */ inputfile.exits())
+
+Temporary files can be created in SwingJS. SwingJS will maintain a pseudo-filesystem for files 
+created with File.createTempFile(). This is useful in that closure of writing to a temporary file 
+does not generate a pseudo-download to the user's machine.
+
+
+UNIMPLEMENTED CLASSES BY DESIGN
+===============================
+
+The SwingJS implementation of the following classes are present 
+in a way that gracefully bypasses their functionality:
+
+accessibility
+security
+serialization
+
+
+
+TODO LIST FOR UNIMPLEMENTED CLASSES
+===================================
+
+JEditorPane (minimal implementation) - DONE 12/2018; some issues still
+JSplitPane - DONE 8/2018
+JTabbedPane - DONE 10/2018
+JTree - done 12/2019
+
+
+MINOR ISSUES--required some rewriting/refactoring by Bob and Udo  
+================================================================
+
+Thread.currentThread() == dispatchThread
+
+
+MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS  
+=====================================================================
+
+See below for a full discussion.
+
+HashMap, Hashtable, and HashSet iterator ordering
+interning, new String("xxx") vs "xxx"
+Names with "$" and "_"
+positive integers do not add to give negative numbers
+ArrayIndexOutOfBounds
+java.awt.Color
+native methods
+javax.swing.JFileDialog
+key focus
+LookAndFeel and UI Classes
+System.exit(0) does not stop all processes
+list cell renderers must be JComponents
+myClass.getField not implemented
+"window" and other reserved JavaScript names
+reserved field and method names
+qualified field and method names
+missing Math methods
+Component.getGraphics(), Graphics.dispose()
+Graphics.setClip()
+
+MAJOR ISSUES--for Bob and Udo within SwingJS
+============================================
+
+fonts
+OS-dependent classes
+AWT component peers
+some aspects of reflection
+
+MAJOR ISSUES--to be resolved by implementers
+============================================
+
+fonts
+threads
+modal dialogs
+image loading
+BigDecimal not fully implemented 
+no format internationalization
+no winding rules
+text-related field implementation
+Formatter/Regex limitations
+integer 1/0 == Infinity
+
+======================================================================== 
+
+DISCUSS
+=======
+
+Table row/col sorter needs checking after removal of java.text.Collator references
+
+I had to move all of SunHints class to RenderingHints, or the 
+two classes could not be loaded. Shouldn't be a problem, I think. The sun classes are
+not accessible to developers in Java anyway, since they are generally package private.
+
+========================================================================== 
+
+//////////////////////////////////////////////////////////////////////////////
+
+UNIMPLEMENTED CLASSES
+=====================
+
+accessibility
+-------------
+
+All Accessibility handling has been commented out to save the download footprint.
+This removes the need for sun.misc.SharedSecrets as well. 
+Nothing says we could not implement accessibility. We just didn't.
+
+
+security
+--------
+
+All JavaScript security is handled by the browser natively. 
+Thus, Java security checking is no longer necessary, and 
+java.security.AccessController has been simplified to work without
+native security checking.
+
+Note that private methods in a class are REALLY private. 
+
+
+serialization
+-------------
+
+All serialization has been removed. It was never very useful for Swing anyway, 
+because one needs exactly the same Java version to save and restore serialized objects.
+
+
+keyboard accelerators and mnemonics
+-----------------------------------
+
+This work was completed in the spring of 2019. Note that in a browser, some 
+key strokes, particularly CTRL-keys, are not available. Bummer.
+
+
+MINOR ISSUES--required some rewriting/refactoring by Bob and Udo  
+================================================================
+
+
+Thread.currentThread() == dispatchThread
+----------------------------------------
+
+changed to JSToolkit.isDispatchThread()
+
+
+MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS  
+=====================================================================
+
+HashMap, Hashtable, and HashSet iterator ordering
+-------------------------------------------------
+
+In Java, iterators for HashMap, Hashtable, and HashSet do not guarantee any particular order. 
+From the HashMap documentation for Java 8:
+
+       This class makes no guarantees as to the order of the map; in particular, it does not 
+       guarantee that the order will remain constant over time.
+Likewise, for HashSet (because it is simply a convenience method for HashMap<Object,PRESENT>:
+
+       [HashSet] makes no guarantees as to the iteration order of the set.
+
+JavaScript's Map object is different. It is basically a LinkedHashMap, so it guarantees iteration
+in order of object addition.
+
+Starting with java2script 3.2.9.v1, these classes use the JavaScript Map object rather than hash codes
+whenever all keys are strictly of JavaScript typeof "string". If any key is introduced that is not a string, the
+implementation falls back to using hash codes, the same as Java. 
+
+Note strings created using new String("xxxx") are NOT typeof "string"; they are typeof "object".
+
+The result is significantly faster performance (3-12 x faster) than originally, and up to 3 x faster
+performance in JavaScript than in Java itself. Right. Faster than Java. 
+
+The JavaScript Map implementation is implemented UNLESS the constructor used is the one that
+specifies both initial capacity and load factor in their constructor. Thus, 
+
+new Hashtable()
+new HashMap()
+new HashMap(16)
+new HashSet()
+
+all use the JavaScript Map. But
+
+new Hashtable(11, 0.75f)
+new HashMap(16, 0.75f)
+new HashSet(16, 0.75f)
+
+do not. 
+
+This design allows for opting out of the JavaScript Map use in order to retain the exact behavior of 
+iterators in JavaScript as in Java.
+
+
+interning, new String("xxx") vs "xxx"
+-------------------------------------
+
+Note that the following are true in JavaScript:
+
+typeof new String("xxxx") == "object"
+typeof "xxxx" == "string"
+var s = "x";typeof ("xxx" + s) == "string"
+
+There is no equivalence to this behavior in Java, where a String is a String is a String.
+
+Be aware that SwingJS does not always create a JavaScript String object using JavaScript's 
+new String(...) constructor. It only does this for Java new String("xxxx") or new String(new String()). 
+
+In all other cases, new String(...) (in Java) results in a simple "xxxx" string in JavaScript. 
+That is, it will be JavaScript typeof "string", not typeof "object". 
+
+The reason for this design is that several classes in the Java core use toString() 
+methods that return new String(), and those classes that do that would cause a JavaScript error
+if implicitly stringified if new String() returned a JavaScript String object. 
+
+This is fine in JavaScript
+
+test1 = function() { return { toString:function(){ return "OK" } } }
+"testing" + new test1()
+>> "testingOK"
+
+But for whatever reason in JavaScript:
+
+test2 = function() { return { toString:function(){ return new String("OK") } } }
+"testing" + new test2()
+>> Uncaught TypeError: Cannot convert object to primitive value
+
+The lesson here is never to use 
+
+  return new String("...");
+
+in a Java toString() method. In Java it will be fine; in JavaScript it will also be fine as long as
+that method is never called in JavaScript implicitly in the context of string concatenation.
+
+A note about interning. Consider the following six Java constructions, where we have a == "x";
+
+"xxx"
+"xx" + "x"
+new String("xxx").intern()
+
+new String("xxx")
+"xx" + a.toString()
+"xx" + a
+
+All six of these will return java.lang.String for .getClass().getName().
+However, the first three are String literals, while the last three are String objects. 
+Thus:
+        "xxx" == "xxx"
+        "xxx" == "xx" + "x"
+        "xxx" == new String("xxx").intern()
+
+but none of the other three are equivalent to "xxx" or each other:
+
+              "xxx" != new String("xxx")
+              "xxx" != "xx" + a.toString()
+              "xxx" != "xx" + a
+  new String("xxx") != new String("xxx") 
+           "xx" + a != new String("xxx") 
+
+etc.
+
+As in Java, in SwingJS, all of the following Java assertions pass as true:
+
+               assert("xxx" == "xx" + "x"); 
+               assert("xxx" == ("xx" + a).intern()); 
+               assert("xxx" === new String("xxx").intern()); 
+               
+and both of these do as well:
+
+               assert(new String("xxx") != "xxx"); 
+               assert(new String("xxx") != new String("xxx")); 
+
+But the following two fail to assert true:
+
+        assert("xxx" != "xx" + a);
+        assert("xxx" != "xx" + a.toString());
+
+because in JavaScript, both of these right-side expressions evaluate to a simple "interned" string.
+
+In Java, however, these assertions are true because Java implicitly "boxes" String 
+concatentaion as a String object, not a literal. 
+
+Most of us know not to generally use == with Strings unless they are explicitly interned. 
+Where this problem may arise, though, is in IdentityHashMap, which compares objects using 
+System.identityHashCode(), which is not the same for different objects or their string literal equivalents.
+
+My recommendation, if you need to use IdentityHashMap with strings is to always use an explicit String.intern()
+for any keys -- unless you really want to keep every string as separate keys even if they are the same sequence, 
+in which case, use new String(). This will work in Java and in  JavaScript.
+
+Be aware when working with strings that come from SwingJS and are being used by other JavaScript modules
+that those that are String objects will return "object" for the JavaScript typeof operator, not "string".
+
+The easy way to ensure this is no problem is to concatenate strings with "" to force immediate interning:
+
+  var x = aJavaObject.getString() + "";
+
+unless you are certain that the string is being returned is a raw JavaScript string.   
+
+Names with "$" and "_"
+----------------------
+
+For the most part, this should be no problem. 
+
+Note that the use of $ and _ in Java field names has always been discouraged:
+[https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html]
+
+       You may find some situations where auto-generated names will contain the dollar sign, 
+       but your variable names should always avoid using it. A similar convention 
+       exists for the underscore character; while it's technically legal to begin your 
+       variable's name with "_", this practice is discouraged.
+
+Some impacts of transpiling method names with full qualification:
+
+1) SwingJS will introduce fields that start with $ or _. These will not conflict
+   if the above convention is followed.
+   
+2) Fields that have the same Java name as a method are not an issue. 
+
+3) Fields that have a Java name with $ that matches a transpiled method name, 
+   such as toString$, will need to be refactored in Java to not have that name collision.
+   
+4) Fields in a subclass that have the same name as private fields in a superclass
+   represent a name collision, because the superclass method needs to call its private
+   field even if invoked from a subclass. The solution was to modify the subclass field
+   name using one or more prepended $.
+   
+5) Use of Class.getDeclaredMethods() reflection will return Method objects having the transpiled 
+   name, not the Java name. This could require some j2sNative adjustment 
+   to strip the $... parameters from the name if that is needed. 
+
+6) Use of Method.getParameterTypes() should work fine, provided class names
+   do not contain "_". This is because the transpiler converts "." to "_" when
+   creating the fully qualified JavaScript name.
+
+
+positive integers do not add to give negative numbers
+-----------------------------------------------------
+
+In Java, the following is true:
+
+  2000000000 + 2000000000 == -294967296
+
+But in SwingJS, that will be 4000000000. So, for example, the following
+strategy will fail in SwingJS:
+
+               int newLength = lineBuf.length * 2;
+               if (newLength < 0) {
+                       newLength = Integer.MAX_VALUE;
+               }
+
+"-1" in JavaScript is not 0xFFFFFFFF.
+
+And one must take care to not compare a negative number with a 32-bit mask. So
+
+(b & 0xFF000000) == 0xFF000000
+
+is true in Java for (int) b = -1, but is false in JavaScript, because 0xFF000000 is 4278190080, 
+while (-1 & 0xFF000000) is, strangely enough, -16777216, and, in fact, 
+
+(0xFF000000 & 0xFF000000) != 0xFF000000
+
+because -16777216 is not 4278190080.
+
+The fix is that one must compare similar operations:
+
+if ((b & 0xFF000000) == (0xFF000000 & 0xFF000000)) .....
+
+Importantly, the JavaScript Int32Array does behave properly. From 
+the Firefox developer console:
+
+>> x = new Int32Array(1)
+<- Int32Array(1) [ 0 ]
+>> x[0] = 4000000000
+<- 4000000000
+>> x[0]
+<- -294967296
+
+Notice that, perhaps unexpectedly, the following two constructs produce 
+different results in JavaScript:
+
+x = new Int32Array(1);
+b = x[0] = 4000000000;
+
+(b will be 4000000000)
+
+and
+
+x = new Int32Array(1);
+x[0] = 4000000000;
+b = x[0];
+
+(b will be -294967296)
+
+
+SwingJS leverages array typing to handle all byte and short arithmetic so as
+to ensure that any byte or short operation in JavaScript does give the same 
+result in Java. The design decision to not also do this with integer math was
+a trade-off between performance and handling edge cases.
+
+
+ArrayIndexOutOfBounds
+---------------------
+
+You cannot implicitly throw an ArrayIndexOutOfBoundsException in JavaScript.
+JavaScript will simply return "undefined", not throw an Exception. So:
+
+boolean notAGoodIdeaIsOutOfBounds(String[] sa, int i) {
+  try {
+     return (sa[i] == sa[i]);
+  } catch (ArrayIndexOutOfBoundsException e) {
+       return false;
+  }
+}
+
+will work in Java but not in JavaScript. Code should not depend upon this sort 
+of trap anyway, if you ask me. 
+
+Throwable vs Error vs Exception
+-------------------------------
+
+True JavaScript errors are trapped as Throwable, whereas you can still trap
+Error and Exception as well. So if you want to be sure to catch any JavaScript
+error, use try{}catch (Throwable t){}, not try{}catch (Exception e){}. 
+
+j
+ava.awt.Color
+--------------
+
+ColorSpace: only "support" CS_sRGB.
+
+ TODO -- any volunteers??
+
+javax.swing.JFileDialog
+-----------------------
+
+HTML5 cannot expose a file reading directory structure. But you certainly 
+can still do file reading and writing. It just works a little differently.
+It's a simple modification:
+
+               b = new JButton("FileOpenDialog");
+               b.addActionListener(new ActionListener() {
+
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               JFileChooser fc = new JFileChooser();
+                               Test_Dialog.this.onDialogReturn(fc.showOpenDialog(Test_Dialog.this));
+                               // Java will wait until the dialog is closed, then enter the onDialogReturn method.
+                               // JavaScript will exit with NaN immediately, and then call back with its actual value
+                               // asynchronously.
+                       }
+
+               });
+       
+               public void onDialogReturn(int value) {
+                       if (value != Math.floor(value))
+                               return; // in JavaScript, this will be NaN, indicating the dialog has been opened
+                       // If we are here, the dialog has closed, in both Java and JavaScript.
+                       System.out.println("int value is " + value);
+               }
+
+
+       @Override
+       public void propertyChange(PropertyChangeEvent event) {
+               Object val = event.getNewValue();
+               String name = event.getPropertyName();
+               System.out.println(name);
+               switch (event.getSource().getClass().getName()) {
+               case "javax.swing.JOptionPane":
+                       switch (name) {
+                       case "inputValue":
+                               onDialogReturn(val);
+                               return;
+                       case "value":
+                               if (val instanceof Integer)
+                                       onDialogReturn(((Integer) val).intValue());
+                               else
+                                       onDialogReturn(val);
+                               return;
+                       }
+                       break;
+               case "javax.swing.ColorChooserDialog":
+                       switch (name) {
+                       case "SelectedColor":
+                               onDialogReturn(val);
+                               return;
+                       }
+                       break;
+               case "javax.swing.JFileChooser":
+                       switch (name) {
+                       case "SelectedFile":
+                               File file = (File) val;
+                               byte[] array = (val == null ? null : /** @j2sNative file.秘bytes || */
+                                               null);
+                               onDialogReturn("fileName is '" + file.getName() + "'\n\n" + new String(array));
+                               return;
+                       }
+                       break;
+               }
+               System.out.println(
+                               event.getSource().getClass().getName() + " " + event.getPropertyName() + ": " + event.getNewValue());
+       }
+
+
+Developers are encouraged to create a separate class that handles general calls to JFileDialog. 
+An example class can be found in the SwingJS distribution as 
+
+/sources/net.sf.j2s.java.core/src/javajs/async/AsyncFileChooser.java.
+
+
+javax.swing.JOptionPane dialogs
+-------------------------------
+
+For this action to work, the parentComponent must implement
+propertyChangeListener, and any call to JOptionPanel should allow for
+an asynchronous response, meaning that there is no actionable code following the
+call to the dialog opening. 
+
+In addition, for compatibility with the Java version, implementation should
+wrap the call to getConfirmDialog or getOptionDialog in a method call to
+handle the Java:
+
+onDialogReturn(JOptionPane.showConfirmDialog(parentFrame,
+messageOrMessagePanel, "title", JOptionPane.OK_CANCEL_OPTION));
+
+Then parentFrame.propertyChange(event) should also call onDialogReturn.
+
+This will then work in both Java and JavaScript.
+
+Note that there is an int and an Object version of onDialogReturn().
+
+
+In JavaScript:
+
+The initial return from JOptionPane.showConfirmDialog and showMessageDialog
+will be (SwingJS) JDialog.ASYNCHRONOUS_INTEGER (NaN), testable as an impossible 
+Java int value using ret != -(-ret) if the parent implements PropertyChangeListener, or -1
+(CLOSE_OPTION) if not.
+
+For showOptionDialog (which returns Object) or showInputDialog (which returns
+String), the initial return will be (SwingJS) JDialog.ASYNCHRONOUS_OBJECT, testable as
+((Object) ret) instanceof javax.swing.plaf.UIResource if the parent implements
+PropertyChangeListeneer, or null if not.
+
+The second return will be the desired return.
+
+In Java:
+
+The initial return will be the one and only modal final return.
+
+
+
+For full compatibility, The calling method must not continue beyond this
+call.
+
+All of the standard Java events associated with Components are also
+available.
+
+Certain fall back mechanisms are possible, where onReturn does not exist, but
+only for the following cases:
+
+
+For showMessageDialog, for WARNING_MESSAGE and ERROR_MESSAGE, a simple
+JavaScript alert() is used, returning 0 (OK_OPTION) or -1 (CLOSED_OPTION).
+
+For showInputDialog, if the message is a string, a simple JavaScript prompt()
+with input box is used, returning the entered string or null.
+
+For showConfirmDialog, a simple JavaScript confirm() is used, in which case:
+
+for YES_NO_OPTION: YES_OPTION or NO_OPTION
+
+for YES_NO_CANCEL_OPTION: YES_OPTION or CANCEL_OPTION
+
+for OK_CANCEL_OPTION or any other: OK_OPTION or CANCEL_OPTION
+
+Note that you should implement a response for CLOSED_OPTION for
+showConfirmDialog. For other dialogs, a null return indicates the dialog was
+closed, just as for Java.
+
+Developers are encouraged to create a separate class that handles general calls. 
+An example class can be found in the SwingJS distribution as src/javajs/async/AsyncDialog.java.
+Very simple modifications to the Java allows asynchronous operation using AsyncDialog. Here
+is a simple "do you want to close this frame" example, where you can see that what we have
+done is to set the reply into an ActionListener that is defined in the constructor of 
+the AsyncDisplay object:
+
+// Original:
+//
+//     private void promptQuit() {
+//             int sel = JOptionPane.showConfirmDialog(null, PROMPT_EXIT, NAME, JOptionPane.YES_NO_OPTION);
+//             switch (sel) {
+//             case JOptionPane.YES_OPTION:
+//                     resultsTab.clean();
+//                     seqs.dispose();
+//                     if (fromMain) {
+//                             System.exit(0);
+//                     }
+//                     break;
+//             }
+//     }
+
+       private void promptQuitAsync() {
+               new AsyncDialog(new ActionListener() {
+
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                           int sel = ((AsyncDialog)e.getSource()).getOption();
+                               switch (sel) {
+                               case JOptionPane.YES_OPTION:
+                                       resultsTab.clean();
+                                       seqs.dispose();
+                                       if (fromMain) {
+                                               System.exit(0);
+                                       }
+                                       break;
+                               }
+                       }}).showConfirmDialog(null, PROMPT_EXIT, NAME, JOptionPane.YES_NO_OPTION);
+       }
+
+Very simple! 
+
+
+native methods
+--------------
+
+The J2S compiler ignores all static native method declarations.
+Anything of this nature needs to be implemented in JavaScript if it is needed,
+using j2sNative blocks:
+
+/**
+ * @j2sNative
+ *
+ *    var putYourJavaScriptCodeHere
+ *
+ */
+ Note that if you follow that directly with a {...} block, then 
+ the javadoc code will run in JavaScript, and the {...} code will run in Java.
+key Focus
+---------
+
+As of June, 2019, the keyboard focus manager is fully implemented. 
+The one catch is that JTextPane and JTextArea, which already consume
+VK_TAB in Java, cannot use CTRL-TAB to continue a tabbing cycle around
+the components in a window. Instead, CTRL-TAB is absorbed by the browser. 
+
+
+LookAndFeel and UI Classes
+--------------------------
+
+SwingJS implements the native browser look and feel as swingjs.plaf.HTML5LookAndFeel. 
+There are small differences between all look and feels -- MacOS, Windows, SwingJS.
+
+Expert developers know how to coerce changes in the UI by subclassing the UI for a 
+component. This probably will not work in SwingJS. 
+
+Note that LookAndFeel in Java usually determines canvas size in a Frame because 
+different operating systems (Mac OS vs Windows vs HTML5) will have 
+different edge sizes on their frames. If you want to ensure a component size, 
+use getContentPane().setPreferredSize().
+
+
+System.exit(0) does not stop all processes
+------------------------------------------
+
+Although System.ext(int) has been implemented in JavaScript, it just closes the 
+frames, stops all pending javax.swing.Timer objects in the queue, and runs any 
+threads added using Runtime.getRuntime().addShutdownHook(Thread).
+It may not stop all "threads." So don't rely on that.
+Applications are responsible for shutting down prior to executing System.exit(0). 
+
+
+myClass.getField not implemented
+--------------------------------
+
+java.lang.reflect.Field is implemented minimally. It is not
+certain that Field.getDeclaringClass() will work. If you just want a 
+value of a field, you can do this:
+
+/**
+ *@j2sNative
+ *
+ * return myClass[name]
+ */   
+
+But that is not a java.lang.reflection.Field object.
+
+
+"window" and other reserved JavaScript names
+--------------------------------------------
+
+No reserved top-level JavaScript name is allowed for a package name. So, for example, 
+one must rename packages such as "window" or "document" to names such as "win" or "doc".
+
+reserved field and method names
+-------------------------------
+
+In order to minimize the chance of added SwingJS field and method names colliding with ones 
+developers might use in subclassing Java classes, we have added U+79D8 (first character of Mandarin 
+"secret") to the characters already disrecommended by Java documentation ("$" and "_"). The only problem
+would be if you use that character followed by certain English words in certain classes. For example
+\u79D8canvas for JComponents (in java.awt.JSComponent) and \u79D8byte (in java.io.File).
+
+qualified field and method names
+--------------------------------
+
+Method names in SwingJS are fully qualified, meaning two methods with the same Java name but different
+parameters, such as write(int) and write(double), must not have the same name in JavaScript. (In this
+case, we will have write$I and write$D.) However, in certain cases it may be desirable to leave the
+method names unqualified. In particular, when an interface actually represents a JavaScript object, 
+the transpiler can leave a method name unqualified. The default situation for this is a class name 
+includes ".api.js" (case-sensitive). This means that any method in any class in a package js within 
+a package api, or any private interface js that has an outer interface api, will have all-unqualified
+methods. An example of this is swingjs.plaf.JSComboPopupList, which needs to communicate with a jQuery 
+object directly using the following interface:
+
+       private interface api {
+
+               interface js extends JQueryObject {
+
+                       abstract js j2sCB(Object options);
+
+                       abstract Object[] j2sCB(String method);
+
+                       abstract Object[] j2sCB(String method, Object o);
+
+                       abstract Object[] j2sCB(String method, int i);
+
+                       abstract int j2sCB(String OPTION, String name);
+
+               }
+       }
+
+Notice that all these variants of j2sCB() will call the same method in JavaScript by design.
+
+
+missing Math methods
+--------------------
+
+java.lang.Math is worked out, but some methods are missing, either because they
+involve long integer value that are inaccessible in JavaScript, or because I just
+didn't implement them. This is a result of continued Java development. 
+It is easy enough to add these methods if you have the source. They go into j2sClazz.js, 
+which is combined with other initial libraries into swingjs2.js by build_site.xml
+
+
+Component.getGraphics(), Graphics.dispose()
+-------------------------------------------
+
+Use of component.getGraphics() is discouraged in Java and in SwingJS. 
+Specifically in SwingJS, any call to component.getGraphics() or 
+BufferedImage.createGraphics() or Graphics.create(...) should be matched with graphics.dispose(), 
+particularly when it is called outside the context of a paint(Graphics)
+call from the system. 
+
+If you see your graphics scrolling down the page with each repaint, 
+look for where you have used Component.getGraphics() and not Graphics.dispose().
+For example, this will definitely NOT work in SwingJS:
+
+  this.paint(getGraphics())
+  
+and really should not work in Java, either, as it is technically a resource memory leak.
+
+Instead, if you really do not want to use repaint(), use this:
+
+  Graphics g = getGraphics();
+  paint(g);
+  g.dispose();
+
+
+
+Graphics.setClip()
+------------------
+
+The HTML5 canvas.clip() method is permanent. You can only reset the clip using
+save/restore. This is different from Java, where you can temporarily change it using
+
+  Shape oldClip = Graphics.getClip();
+  Graphics.setClip(newClip);
+   ...
+  Graphics.setClip(oldClip); 
+
+If you need to do something like this, you must schedule the paints
+to not have overlapping clip needs.
+
+
+MAJOR ISSUES--for Bob and Udo within SwingJS
+============================================
+
+fonts
+-----
+
+Fonts and FontMetrics will all be handled in JavaScript. Font matching will 
+not be exact, and composite (drawn) fonts will not be supported. 
+
+SwingJS handles calls such as font.getFontMetrics(g).stringWidth("xxx") by 
+creating a <div> containing that text, placing it in an obscure location on 
+the page, and reading div.getBoundingClientRect(). This is a VERY precise
+value, but can be a pixel or two off from what Java reports for the same font.
+OS-dependent classes
+--------------------
+
+Static classes such as:
+
+   java.awt.Toolkit
+   java.awt.GraphicsEnvironment
+   
+   
+which are created using Class.forName are implemented using classes in the swingjs package.
+
+AWTAccessor is not implemented. 
+
+   
+AWT component peers and component "ui" user interfaces
+------------------------------------------------------
+
+ComponentPeer is a class that represents a native AWT component.
+Components with such peers are called "heavy-weight" components.
+They are expected to do the dirty work of graphics drawing. 
+
+Java Swing implements peers only for JApplet, JDialog, JFrame, and JWindow. 
+References to such objects have been removed, but clearly there must be 
+some connection to similar DOM objects, even for "light-weight" components. 
+
+
+  
+MAJOR ISSUES--to be resolved by implementers
+============================================
+
+fonts
+-----
+
+Glyph/composite/outline fonts are not supported.
+   
+
+
+threads
+-------
+
+Thread locking and synchronization are not relevant to JavaScript.
+Thus, anything requiring "notify.." or "waitFor.." could be a serious issue.
+All threading must be "faked" in JavaScript. Specifically not available is:
+
+  Thread.sleep()
+  
+javax.swing.AbstractButton#doClick(pressTime) will not work, as it requires Thread.sleep();
+    
+However, java.lang.Thread itself is implemented and used extensively. 
+
+Methods thread.start() and thread.run() both work fine. 
+
+For simple applications that use Thread.sleep() just to have a delay, as in a frame rate, for 
+example, one can use javax.swing.Timer instead. That is fully implemented. 
+
+Likewise, java.util.Timer can be replaced with no loss of performance with javax.Swing.Timer.
+Note that java.util.TimerTask is implemented, but it can also be replaced by an implementation of Runnable.
+
+task = new TimerTask(){....};
+t = new java.util.Timer();
+t.schedule(task, 0, 1);
+
+becomes
+
+task = new TimerTask(){....}; // or task = new Runnable() {...}
+t = new javax.swing.Timer(1, new ActionListener() {
+       @Override
+       public void actionPerformed(ActionEvent e) {
+               task.run();
+       }
+};
+t.setInitialDelay(0); // not particularly necessary
+t.start();
+
+In addition, SwingJS provides swingjs.JSThread, which can be subclassed
+if desired. This class allows simple 
+
+  while(!interrupted()){
+       wait()
+       ...
+  }  
+
+action through an asynchronous function run1(mode). For example:
+
+       protected void run1(int mode) {
+               try {
+                       while (true)
+                               switch (mode) {
+                               case INIT:
+                                       // once-through stuff here
+                                       mode = LOOP;
+                                       break;
+                               case LOOP:
+                                       if (!doDispatch || isInterrupted()) {
+                                               mode = DONE;
+                                       } else {
+                                               Runnable r = new Runnable() {
+                                                       public void run() {
+                                                               // put the loop code here
+                                                       }
+                                               };
+                                               dispatchAndReturn(r);
+                                               if (isJS)
+                                                       return;
+                                       }
+                                       break;
+                               // add more cases as needed
+                               case DONE:
+                                       // finish up here
+                                       if (isInterrupted())
+                                               return;
+                                       // or here
+                                       break;
+                               }
+               } finally {
+                       // stuff here to be executed after each loop in JS or at the end in Java
+               }
+       }
+
+image loading
+-------------
+- All image loading in SwingJS is synchronous. A MediaTracker call will immediately return "complete".
+  However, it still may take one system clock tick to fully load images. Thus, it is recommended that
+  images be preloaded in the static block of the applet if it is necessary that they be available in init().
+  This is only an issue if you are trying to access the pixel buffer of the image in JavaScript. 
+  
+- Applet.getImage(path, name) will return null if the image does not exist. 
+
+- BufferedImage: only "support" imageType RGB and ARGB
+
+  -BH: This is a temporary edit, just to get us started. Certainly GRAY will be needed
+
+
+BigInteger and BigDecimal
+-------------------------
+
+java.math.BigInteger is fully supported; java.math.BigDecimal is roughed 
+in and not fully tested (07/2019). 
+
+Both classes present significant issues for JavaScript, as they are based in 
+Java's 64-bit long for all their operations. Here is the JavaDoc note I added
+to BigInteger:
+
+ * SwingJS note: Because of the limitations of JavaScript with regard
+ * to long-integer bit storage as a double, this implementation drops
+ * the integer storage bit length to 24, giving 48 for long and leaving
+ * the last 16 bits clear for the exponent of the double number. This should
+ * not affect performance significantly. It does increase the storage 
+ * size by about 33%. By bringing an "int" to 3 bytes, we can easily construct
+ * and use byte[] data intended for the original BitSet.  
+
+"Easily" may be a bit strong there. This was a serious challenge.
+
+BigDecimal seems to run normally, but in order to do that, my hack involves
+reducing the size of an integer that is allowed to be stored as such and not
+in byte[] as a BigInteger. I'm sure there is a performance hit, but it does work.
+
+no format internationalization
+------------------------------
+
+For now, just en for number and date formatters
+
+no winding rules
+----------------
+
+  When filling a graphic, only nonzero winding rule is implemented in HTML5 Canvas2D.
+
+
+
+text-related field implementation
+---------------------------------
+
+Text fields are:
+
+JTextField   (JavaScript <input type="text">)
+JTextArea    (JavaScript <textarea>)
+JTextPane    (JavaScript <div>)
+JEditorPane  (JavaScript <div>)
+
+For the initial implementation, we don't implement infinite undo/redo, and the abstract 
+document model is much less elaborate. Only PlainDocument (in the form of JSPlainDocument)
+is implemented. The Document returned by JTextField.getDocument() is a javax.swing.text.Document.
+
+All scrolling is handled by HTML5. javax.swing.AutoScroller is not implemented.
+public static methods .stop, .isRunning, .processMouseDragged require true Java threading
+and so are not implmented. javax.swing.text.View and its subclasses are not implemented. 
+
+The JS document model does not allow two text fields to address the same underlying document. 
+
+
+Formatter/Regex limitations
+---------------------------
+
+Some browsers cannot process Regex "look-behind" process such as (?<=\W)
+java.util.regex.Matcher and Pattern use JavaScript's RegExp object rather than
+the native Java object. These are not identical. Only flags /igm are supported.
+Matcher.start(groupID) is not supported.
+
+java.util.Formatter will function correctly for all standard %... patterns.
+
+integer 1/0 == Infinity
+-----------------------
+
+1/0 in Java throws "java.lang.ArithmeticException: / by zero", but in JavaScript is just Infinity. 
+
+
+Summary
+-------
+
+These are all the known limitations of SwingJS. We have not found any of these limitations
+to be show-stoppers. The primary issue for newcomers to SwingJS is having the source code.
+You must check that source code for all your library jar files is available or, if you
+choose, you will need to decompile those classes. We have used decompilation on some projects,
+and it works just fine. So, technically, all we really need are JAR/class files. But the 
+source is by far superior. It's generally prettier, and it has the license information that
+may or may not be present with the JAR or class files. Use class files at your own risk.
+
+Bob Hanson
+2019.08.16
+
+
+Additional Issues
+-----------------
+
+Annotation is working for classes, methods, and fields (12/22/19). Method reflection, however,
+is limited. Interfaces do not expose their methods, as the transpiler does not actually transpile
+the interfaces themselves. And method reflection only includes annotated methods.
+
+java.util.concurrent is not fully elaborated. This package is rewritten to not actually use the
+memory handling capabilities of concurrency, which JavaScript does not have access to.
+
+System.getProperties() just returns a minimal set of properties.
+
+
index 6c92ed6..49cba07 100644 (file)
@@ -1 +1 @@
-20200601093857 
+20200610103554 
index 5563c76..f722183 100644 (file)
Binary files a/swingjs/ver/3.2.9-j11/SwingJS-site.zip and b/swingjs/ver/3.2.9-j11/SwingJS-site.zip differ
diff --git a/swingjs/ver/3.2.9-j11/differences.txt b/swingjs/ver/3.2.9-j11/differences.txt
new file mode 100644 (file)
index 0000000..70eabbc
--- /dev/null
@@ -0,0 +1,1480 @@
+Notes
+=====
+
+---IMPORTANT CHARACTER SET NOTE---
+
+It is critical that all development work in Java2Script 
+be done in UTF-8. This means:
+
+- making sure your Eclipse project is set up for UTF-8 (not the Eclipse default?)
+- making sure your server can serve up UTF-8 by default for any browser-loaded files
+- making sure you don't edit a Java2Script class file or one of the site .js files
+    using a non-UTF-8 editor. It may replace non-Latin characters with "?" or garbage.
+- making sure that your web pages are delivered with proper headings indicating HTML5 and UTF-8
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+
+Note that the DOCTYPE tag is critical for some browsers to switch into HTML5 mode. (MSIE?)
+
+
+
+  
+In particular, the Mandarin character ç§˜ (mi; "secret") is used extensively throughout
+the SwingJS class files to distinguish j2s-specific fields and methods that must not 
+ever be shadowed or overridden by subclasses. For example, we see in java.lang.Thread.java:
+
+               public static JSThread ç§˜thisThread;
+
+----------------------------------
+
+
+updated 3/21/2020 -- adds note about HashMap, Hashtable, and HashSet iterator ordering
+updated 3/20/2020 -- adds note about interning, new String("xxx"), and "xxx"
+updated 2/26/2020 -- adds Graphics.setClip issue
+updated 12/22/19 -- additional issues
+updated 11/03/19 -- adds information about File.exists() and points to src/javajs/async
+updated 10/26/19 -- adds information about File.createTempFile()
+updated 8/16/19 -- minor typos and added summary paragraph
+updated 7/19/19 -- clarification that AWT and Swing classes are supported directly
+updated 5/13/19 -- Mandarin U+79D8 reserved character; Missing Math methods; int and long
+updated 5/10/19 -- adds a section on static issues in multi-(duplicate)-applet pages
+updated 1/4/19 -- nio
+updated 9/15/18 -- adds integer 1/0 == Infinity
+updated 7/24/18 -- most classes replaced with https://github.com/frohoff/jdk8u-jdk
+updated 6/5/17 -- reserved package name "window"
+updated 3/11/17 -- myClass.getField
+updated 3/7/17 -- overloading of JSplitPane.setDividerLocation
+updated 3/2/17 -- more indication of classes not implemented (KeyListener)
+
+=============================================================================
+SwingJS and OpenJDK 8+
+=============================================================================
+
+SwingJS implements a wide range of the Java language in JavaScript. The base
+version for this implementation is OpenJDK8. some classes are implemented using 
+older source code, and there are some missing methods. For the most part, this is 
+no real problem. You can add or modify any java class just be adding it as source
+in your project. Or (preferably) you can contact me, and I can get it into the 
+distribution. Or (even more preferably) you can do that via a patch submission. 
+
+=================
+DESIGN PHILOSOPHY
+=================
+
+The java2script/SwingJS design goal is to recreate a recognizable, easily debuggable
+equivalent in JavaScript for as much of Java as practical. This means, for example, 
+that one can call in JavaScript 
+
+  new java.util.Hashtable()
+  
+and for all practical purposes it will appear that Java is running.
+
+
+Method and Field Disambiguation
+-------------------------------
+
+SwingJS has no problem with the overloading of methods, for example:
+
+  public void print(int b);
+  public void print(float b);
+
+JavaScript does not allow overloading of methods, and the common practice in
+Java of naming a field the same as a method -- isAllowed and isAllowed() -- is
+not possible in JavaScript. As a result, SwingJS implements "fully-qualified" 
+method names using "$" parameter type separation. Thus, these methods in SwingJS
+will be referred to as print$I and print$F. The rules for this encoding are
+relatively simple: 
+
+1. The seven primitive types in Java are encoded $I (int), $L (long), $F (float), 
+$D (double), $B (byte) $Z (boolean), and $H (short). 
+
+2. String and Object are encoded as $S and $O, respectively.
+
+3. "java_lang_" is dropped for all other classes in the java.lang package (as in Java).
+   For example:  $StringBuffer, not $java_lang_StringBuffer
+
+4. All other classes are encoded as 
+
+ "$" + Class.getName().replace(".","_")
+
+For example, in Java we see:
+
+  public void equals(Object o) {...}
+
+Whereas in SwingJS we have:
+
+  Clazz.newMeth(C$, 'equals$O', function (o) {...}
+
+And 
+
+ this.getContentPane().add(bar, "North");
+
+becomes
+
+ this.getContentPane$().add$java_awt_Component$O(bar, "North");
+
+5. Arrays are indicated with appended "A" for each level. So
+
+  setDataVector(Object[][] dataVector, Object[] columnIdentifiers)
+  
+becomes
+
+  setDataVector$OAA$OA(dataVector, columnIdentifiers)
+
+(It is recognized that this design does introduce a bit of ambiguity, in that
+ in principal there could be user class named XA and X in the same package,
+ and methods a(X[]) and a(XA) in the same class that cannot be distinguished.
+ The benefit of this simple system, however, triumphed over the unlikelyhood
+ of that scenario.) The transpiler could be set to flag this possibility.
+
+6. Constructors are prepended with "c$". So 
+
+  public JLabel(String text) {...}
+  
+becomes:
+
+  Clazz.newMeth(C$, 'c$$S', function (text) {...});
+
+Field disambiguation involves prepending. In Java, a class and its subclass 
+can both have the same field name, such as 
+
+ boolean visible;
+When this happens, it is called "shadowing", and though not recommended, Java allows
+it. The Java2Script transpiler will prepend such shadowing fields with "$" so that the
+subclass instance has both "visible" (for use in its methods inherited from its
+superclass) and "$visible" (for its own methods). Thus, we might see in Java:
+
+  this.visible = super.visible;
+  
+while in SwingJS we will see:
+
+  this.$visible=this.visible;
+
+since JavaScript does not have the "super" keyword.
+
+
+
+Parameterless methods such as toString() are appended with "$" to become toString$().
+The one exception to this rule is private methods, which are saved in (truly) private 
+array in the class (and are not accessible by reflection). Private parameterless 
+methods retain their simple Java name, since they cannot conflict with field names.
+
+This renaming of methods has a few consequences, which are discussed more fully below.
+See particularly the section on "qualified field and method names", where it is described
+how you can use packages or classes or interfaces with ".api.js" in them to represent JavaScript
+objects for which all method names are to be left unqualified. Note that it is not 
+possible to cherry-pick methods to be unqualified; only full packages, classes or 
+interfaces can hold this status.
+
+The swingjs.api.js package in particular contains a number of useful interfaces that
+you can import into your project for JavaScript-specific capabilities.
+
+
+Applet vs. Application
+----------------------
+
+One of the very cool aspects of SwingJS is that it doesn't particularly matter if a browser-based
+Java app is an "applet" or an "application". We don't need JNLP (Java Network Launch Protocol) 
+because now we can just start up any Java application in a browser just as easily as any applet.
+The associative array that passes information to the SwingJS applet (information that formerly
+might have been part of the APPLET tag, such as width, height, and codebase, always referred to 
+in our writing as "the Info array") allows the option to specify the JApplet/Applet "code" 
+class or the application "main" class. Either one will run just fine.
+
+
+Performance
+-----------
+
+Obviously, there are limitations. One is performance, but we have seen reproducible 
+performance at 1/6 - 1/3 the speed of Java. Achieving this performance may require
+some refactoring of the Java to make it more efficient in both Java and JavaScript. 
+"for" loops need to be more carefully crafted; use of "new" and "instanceof" need to be
+minimized in critical areas. Note that method overloading -- that is, the same method name
+with different parameters, such as read(int) and read(byte) -- is no longer any problem. 
+  
+
+Threads
+-------
+
+Although there is only a single thread in JavaScript, meaning Thread.wait(), Thread.sleep(int) and 
+Thread.notify() cannot be reproduced, we have found that this is not a serious limitation. 
+For example, javax.swing.Timer() works perfectly in JavaScript. All it means is that threads 
+that use sleep(int) or notify() must be refactored to allow Timer-like callbacks. That is, 
+they must allow full exit and re-entry of Thread.run(), not the typical while/sleep motif. 
+
+The key is to create a state-based run() that can be exited and re-entered in JavaScript.
+
+
+Static fields
+-------------
+
+Final static primitive "constant" fields (String, boolean, int, etc.) such as 
+
+static final int TEST = 3;
+static final String MY_STRING = "my " + "string";
+
+are converted to their primitive form automatically by the Eclipse Java compiler 
+and do not appear in the JavaScript by their names. 
+
+Other static fields are properties of their class and can be used as expected.
+
+Note, however, that SwingJS runs all "Java" code on a page in a common "jvm" 
+(like older versions of Java). So, like the older Java schema, the JavaScript 
+equivalents of both applets and applications will share all of their static 
+fields and methods. This includes java.lang.System. 
+
+Basically, SwingJS implementations of Java run in a browser page-based sandbox 
+instead of an applet-specific one.
+
+In general, this is no problem. But if we are to implement pages with 
+multiple applets present, we must be sure to only have static references 
+that are "final" or specifically meant to be shared in a JavaScript 
+environment only (since they will not be shared in Java).
+
+A simple solution, if static non-constant references are needed, is to attach the 
+field to Thread.currentThread.threadGroup(), which is an applet-specific reference.
+Be sure, if you do this, that you use explicit setters and getters:
+
+For example, 
+
+private static String myvar;
+
+...
+
+public void setMyVar(String x) {
+  ThreadGroup g = Thread.currentThread().threadGroup();
+  /**
+   * @j2sNative g._myvar = x;
+   * 
+   */
+   {
+     myvar = x;
+   }
+}
+
+public String getMyVar() {
+  ThreadGroup g = Thread.currentThread().threadGroup();
+  /**
+   * @j2sNative return g._myvar || null;
+   * 
+   */
+   {
+     return myvar;
+   }
+}
+ in Java will get and set x the same in JavaScript and in Java. 
+A convenient way to do this in general is to supply a singleton class with
+explicitly private-only constructors and then refer to it in Java and in JavaScript
+instead of using static field, referring to myclass.getIntance().xxx instead of 
+myclass.xxx in Java (and JavaScript). 
+
+This was done extensively in the Jalview project. See jalview.bin.Instance.
+
+
+Helper Packages -- swingjs/ and javajs/
+---------------------------------------
+
+The SwingJS library is the swingjs/ package. There are interfaces that may be of assistance
+in swingjs/api, but other than that, it is not recommended that developers access classes in 
+this package. The "public" nature of their methods is really an internal necessity.
+
+In addition to swingjs/, though, there are several useful classes in the javajs/ package
+that could be very useful. This package is a stand-alone package that can be 
+cloned in any Java project that also would be great to have in any JavaScript project
+-- SwingJS-related or not. Functionality ranges from reading and writing various file 
+formats, including PDF, BMP, PNG, GIF, JPG, JSON, ZIP, and CompoundDocument formats.
+
+A variety of highly efficient three- and four-dimensional point, vector, matrix, and 
+quaternion classes are included, as they were developed for JSmol and inherited from that
+project. 
+
+Of particular interest should be javajs/async/, which includes
+
+javajs.async.Async
+javajs.async.AsyncColorChooser
+javajs.async.AsyncDialog
+javajs.async.AsyncFileChooser
+
+See javajs.async.Async JavaDoc comments for a full description of 
+these useful classes.
+
+
+Modal Dialogs
+-------------
+
+Although true modal dialogs are not possible with only one thread, a functional equivalent -- 
+asynchronous modal dialogs -- is relatively easy to set up. All the JOptionPane dialogs will
+return PropertyChangeEvents to signal that they have been disposed of and containing the results. 
+See below and classes in the javajs.async package.
+
+
+Native calls
+------------
+
+Native calls in Java are calls to operating system methods that are not in Java. JavaScript
+has no access to these, of course, and they must all be replaced by JavaScript equivalents.
+Fortunately, they are not common, and those that are present in Java (for example, in calculating
+checksums in ZIP file creation) are at a low enough level that most developers do not utilize them
+or do not even have access to them. All native calls in Java classes have been replaced by 
+Java equivalents.
+
+
+Swing GUI Peers and UIClasses
+-----------------------------
+
+One of the biggest adaptations introduced in SwingJS is in the area of the graphical 
+user interface. The issue here is complex but workable. In Java there are two background 
+concepts -- the Component "peer" (one per "heavy-weight" component, such as a Frame) and the 
+component "uiClass" (one per component, such as JButton or JTextField).
+
+Peers are native objects of the operating system. These are the virtual buttons and text areas
+that the user is interacting with at a very base level. Their events are being passed on to 
+Java or the browser by the operating system. UI classes provide a consistent "look and feel" 
+for these native objects, rendering them onto the native window canvas and handling all 
+user-generated events. They paint the borders, the backgrounds, the highlights, of every 
+control you see in Java. There is one-to-one correspondence of Swing classes and UI classes. 
+Setting the Look and Feel for a project amounts to selecting the directory from which to draw 
+these UI classes. The UI classes can be found in the javax.swing.plaf ("platform look and feel") 
+package.
+
+Early on in the development of SwingJS, we decided not to fully reproduce the painfully detailed 
+bit-by-bit painting of controls as is done in Java. Instead, we felt it was wiser to utilize the standard
+HTML5 UI capabilities as much as possible, using DIV, and INPUT especially, with extensive use
+of CSS and sometimes jQuery (menus, and sliders, for example). Thus, we have created a new 
+set of UIs -- the "HTML5 Look and Feel". These classes can be found in swingjs.plaf. Besides being
+more adaptable, this approach allows far more versatility to SwingJS developers, allowing them
+to modify the GUI to suit their needs if desired.
+
+In SwingJS, since we have no access to native peers except through the browser DOM,
+it seemed logical to merge the peer and UI idea. So instead of having one peer per heavy-weight control and
+one UI class instance for each control type, we just have one UI class instance per control, and
+that UI class instance is what is being referred to when a "peer" is notified. 
+
+In some ways this is a throw back to when all of Swing's components were subclasses of
+specific AWT components such as Button and List. These "heavy-weight components" all had their 
+own individual native peers and thus automatically took on the look and feel provided by the OS. 
+Later Swing versions implemented full look and feel for all peers, leaving only JDialog, JFrame,
+and a few other classes to have native peers. But in SwingJS we have again a 1:1 map of component
+and UI class/peer instance.
+
+The origin of most issues (read "bugs") in relation to the GUI will probably be found in the
+swingjs.plaf JSxxxxUI.java code.
+
+  
+Swing-only Components -- no longer an issue
+-------------------------------------------
+
+Swing was introduced into Java well after the Java Abstract Window Toolkit (AWT) was well
+established. As such, its designers chose to allow AWT controls such as Button and List to be used 
+alongside their Swing counterparts JButton and JList. Reading the code, it is clear that this 
+design choice posed a huge headache for Swing class developers. 
+
+For SwingJS, we decided from the beginning NOT to allow this mixed-mode programming and 
+instead to require that all components be Swing components. 
+
+However, this is no longer an issue. All AWT components in SwingJS are now subclasses of 
+javax.swing.JComponent. So far, we have found no problem with this.
+
+The a2s Adapter Package
+-----------------------
+
+Originally, we thought that we would restrict ourselves to JApplets only. That is, only
+Swing-based applets. But as we worked, we discovered that there are a lot of great 
+applets out there that are pre-Swing pure-AWT java.applet.Applet applets. Our problem was 
+that we also wanted it to be possible to quickly adapt these applets to JavaScript as well.
+The solution turned out to be simple: Write a package (a2s) that recreates the interface for 
+non-Swing components as subclasses of Swing components. Thus, a2s.Button subclasses javax.swing.JButton
+but also accepts all of the methods of java.awt.Button. This works amazingly well, with a few
+special adaptations to the core javax.swing to be "AWT-aware." All AWT components now subclass 
+a2s components, which in turn subclass JComponents. So no changes in code are necessary. We have
+successfully transpiled over 500 applets using this strategy. (Kind of surprising, actually, that
+the original Java developers did not see that option. But we have a hindsight advantage here.)
+
+
+Working with Files
+==================
+
+Simple String file names are not optimal for passing information about
+read files within SwingJS applications. 
+All work with files should either use Path or File objects exclusively. 
+These objects, after a file is read or checked for existence, will already 
+contain the file byte[] data. Doing something like this:
+
+File f = File("./test.dat");
+boolean isOK = f.exists();
+
+will load f with its byte[] data, if the file exists. 
+
+But if after that, we use:
+
+File f2 = new File(f.getAbsolutePath());
+
+f2 will not contain that data. Such copying should be done as:
+
+File f2 = new File(f);
+
+in which case, the byte[] data will be transferred.
+
+
+SwingJS uses the following criteria to determine if File.exists() returns true:
+
+(1) if this File object has been used directly to read data, or 
+(2) if reading data using this File object is successful.
+
+Note that you cannot check to see if a file exists before input or if it 
+was actually written or if it already exists prior to writing in SwingJS.  
+
+Thus, you should check each use of file.exists() carefully, and if necessary, provide a J2sNative 
+block that gives an appropriate "OK" message, for example:
+
+(/** @j2sNative 1 ? false : */ outputfile.exits())
+
+or 
+
+(/** @j2sNative 1 ? true : */ inputfile.exits())
+
+Temporary files can be created in SwingJS. SwingJS will maintain a pseudo-filesystem for files 
+created with File.createTempFile(). This is useful in that closure of writing to a temporary file 
+does not generate a pseudo-download to the user's machine.
+
+
+UNIMPLEMENTED CLASSES BY DESIGN
+===============================
+
+The SwingJS implementation of the following classes are present 
+in a way that gracefully bypasses their functionality:
+
+accessibility
+security
+serialization
+
+
+
+TODO LIST FOR UNIMPLEMENTED CLASSES
+===================================
+
+JEditorPane (minimal implementation) - DONE 12/2018; some issues still
+JSplitPane - DONE 8/2018
+JTabbedPane - DONE 10/2018
+JTree - done 12/2019
+
+
+MINOR ISSUES--required some rewriting/refactoring by Bob and Udo  
+================================================================
+
+Thread.currentThread() == dispatchThread
+
+
+MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS  
+=====================================================================
+
+See below for a full discussion.
+
+HashMap, Hashtable, and HashSet iterator ordering
+interning, new String("xxx") vs "xxx"
+Names with "$" and "_"
+positive integers do not add to give negative numbers
+ArrayIndexOutOfBounds
+java.awt.Color
+native methods
+javax.swing.JFileDialog
+key focus
+LookAndFeel and UI Classes
+System.exit(0) does not stop all processes
+list cell renderers must be JComponents
+myClass.getField not implemented
+"window" and other reserved JavaScript names
+reserved field and method names
+qualified field and method names
+missing Math methods
+Component.getGraphics(), Graphics.dispose()
+Graphics.setClip()
+
+MAJOR ISSUES--for Bob and Udo within SwingJS
+============================================
+
+fonts
+OS-dependent classes
+AWT component peers
+some aspects of reflection
+
+MAJOR ISSUES--to be resolved by implementers
+============================================
+
+fonts
+threads
+modal dialogs
+image loading
+BigDecimal not fully implemented 
+no format internationalization
+no winding rules
+text-related field implementation
+Formatter/Regex limitations
+integer 1/0 == Infinity
+
+======================================================================== 
+
+DISCUSS
+=======
+
+Table row/col sorter needs checking after removal of java.text.Collator references
+
+I had to move all of SunHints class to RenderingHints, or the 
+two classes could not be loaded. Shouldn't be a problem, I think. The sun classes are
+not accessible to developers in Java anyway, since they are generally package private.
+
+========================================================================== 
+
+//////////////////////////////////////////////////////////////////////////////
+
+UNIMPLEMENTED CLASSES
+=====================
+
+accessibility
+-------------
+
+All Accessibility handling has been commented out to save the download footprint.
+This removes the need for sun.misc.SharedSecrets as well. 
+Nothing says we could not implement accessibility. We just didn't.
+
+
+security
+--------
+
+All JavaScript security is handled by the browser natively. 
+Thus, Java security checking is no longer necessary, and 
+java.security.AccessController has been simplified to work without
+native security checking.
+
+Note that private methods in a class are REALLY private. 
+
+
+serialization
+-------------
+
+All serialization has been removed. It was never very useful for Swing anyway, 
+because one needs exactly the same Java version to save and restore serialized objects.
+
+
+keyboard accelerators and mnemonics
+-----------------------------------
+
+This work was completed in the spring of 2019. Note that in a browser, some 
+key strokes, particularly CTRL-keys, are not available. Bummer.
+
+
+MINOR ISSUES--required some rewriting/refactoring by Bob and Udo  
+================================================================
+
+
+Thread.currentThread() == dispatchThread
+----------------------------------------
+
+changed to JSToolkit.isDispatchThread()
+
+
+MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS  
+=====================================================================
+
+HashMap, Hashtable, and HashSet iterator ordering
+-------------------------------------------------
+
+In Java, iterators for HashMap, Hashtable, and HashSet do not guarantee any particular order. 
+From the HashMap documentation for Java 8:
+
+       This class makes no guarantees as to the order of the map; in particular, it does not 
+       guarantee that the order will remain constant over time.
+Likewise, for HashSet (because it is simply a convenience method for HashMap<Object,PRESENT>:
+
+       [HashSet] makes no guarantees as to the iteration order of the set.
+
+JavaScript's Map object is different. It is basically a LinkedHashMap, so it guarantees iteration
+in order of object addition.
+
+Starting with java2script 3.2.9.v1, these classes use the JavaScript Map object rather than hash codes
+whenever all keys are strictly of JavaScript typeof "string". If any key is introduced that is not a string, the
+implementation falls back to using hash codes, the same as Java. 
+
+Note strings created using new String("xxxx") are NOT typeof "string"; they are typeof "object".
+
+The result is significantly faster performance (3-12 x faster) than originally, and up to 3 x faster
+performance in JavaScript than in Java itself. Right. Faster than Java. 
+
+The JavaScript Map implementation is implemented UNLESS the constructor used is the one that
+specifies both initial capacity and load factor in their constructor. Thus, 
+
+new Hashtable()
+new HashMap()
+new HashMap(16)
+new HashSet()
+
+all use the JavaScript Map. But
+
+new Hashtable(11, 0.75f)
+new HashMap(16, 0.75f)
+new HashSet(16, 0.75f)
+
+do not. 
+
+This design allows for opting out of the JavaScript Map use in order to retain the exact behavior of 
+iterators in JavaScript as in Java.
+
+
+interning, new String("xxx") vs "xxx"
+-------------------------------------
+
+Note that the following are true in JavaScript:
+
+typeof new String("xxxx") == "object"
+typeof "xxxx" == "string"
+var s = "x";typeof ("xxx" + s) == "string"
+
+There is no equivalence to this behavior in Java, where a String is a String is a String.
+
+Be aware that SwingJS does not always create a JavaScript String object using JavaScript's 
+new String(...) constructor. It only does this for Java new String("xxxx") or new String(new String()). 
+
+In all other cases, new String(...) (in Java) results in a simple "xxxx" string in JavaScript. 
+That is, it will be JavaScript typeof "string", not typeof "object". 
+
+The reason for this design is that several classes in the Java core use toString() 
+methods that return new String(), and those classes that do that would cause a JavaScript error
+if implicitly stringified if new String() returned a JavaScript String object. 
+
+This is fine in JavaScript
+
+test1 = function() { return { toString:function(){ return "OK" } } }
+"testing" + new test1()
+>> "testingOK"
+
+But for whatever reason in JavaScript:
+
+test2 = function() { return { toString:function(){ return new String("OK") } } }
+"testing" + new test2()
+>> Uncaught TypeError: Cannot convert object to primitive value
+
+The lesson here is never to use 
+
+  return new String("...");
+
+in a Java toString() method. In Java it will be fine; in JavaScript it will also be fine as long as
+that method is never called in JavaScript implicitly in the context of string concatenation.
+
+A note about interning. Consider the following six Java constructions, where we have a == "x";
+
+"xxx"
+"xx" + "x"
+new String("xxx").intern()
+
+new String("xxx")
+"xx" + a.toString()
+"xx" + a
+
+All six of these will return java.lang.String for .getClass().getName().
+However, the first three are String literals, while the last three are String objects. 
+Thus:
+        "xxx" == "xxx"
+        "xxx" == "xx" + "x"
+        "xxx" == new String("xxx").intern()
+
+but none of the other three are equivalent to "xxx" or each other:
+
+              "xxx" != new String("xxx")
+              "xxx" != "xx" + a.toString()
+              "xxx" != "xx" + a
+  new String("xxx") != new String("xxx") 
+           "xx" + a != new String("xxx") 
+
+etc.
+
+As in Java, in SwingJS, all of the following Java assertions pass as true:
+
+               assert("xxx" == "xx" + "x"); 
+               assert("xxx" == ("xx" + a).intern()); 
+               assert("xxx" === new String("xxx").intern()); 
+               
+and both of these do as well:
+
+               assert(new String("xxx") != "xxx"); 
+               assert(new String("xxx") != new String("xxx")); 
+
+But the following two fail to assert true:
+
+        assert("xxx" != "xx" + a);
+        assert("xxx" != "xx" + a.toString());
+
+because in JavaScript, both of these right-side expressions evaluate to a simple "interned" string.
+
+In Java, however, these assertions are true because Java implicitly "boxes" String 
+concatentaion as a String object, not a literal. 
+
+Most of us know not to generally use == with Strings unless they are explicitly interned. 
+Where this problem may arise, though, is in IdentityHashMap, which compares objects using 
+System.identityHashCode(), which is not the same for different objects or their string literal equivalents.
+
+My recommendation, if you need to use IdentityHashMap with strings is to always use an explicit String.intern()
+for any keys -- unless you really want to keep every string as separate keys even if they are the same sequence, 
+in which case, use new String(). This will work in Java and in  JavaScript.
+
+Be aware when working with strings that come from SwingJS and are being used by other JavaScript modules
+that those that are String objects will return "object" for the JavaScript typeof operator, not "string".
+
+The easy way to ensure this is no problem is to concatenate strings with "" to force immediate interning:
+
+  var x = aJavaObject.getString() + "";
+
+unless you are certain that the string is being returned is a raw JavaScript string.   
+
+Names with "$" and "_"
+----------------------
+
+For the most part, this should be no problem. 
+
+Note that the use of $ and _ in Java field names has always been discouraged:
+[https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html]
+
+       You may find some situations where auto-generated names will contain the dollar sign, 
+       but your variable names should always avoid using it. A similar convention 
+       exists for the underscore character; while it's technically legal to begin your 
+       variable's name with "_", this practice is discouraged.
+
+Some impacts of transpiling method names with full qualification:
+
+1) SwingJS will introduce fields that start with $ or _. These will not conflict
+   if the above convention is followed.
+   
+2) Fields that have the same Java name as a method are not an issue. 
+
+3) Fields that have a Java name with $ that matches a transpiled method name, 
+   such as toString$, will need to be refactored in Java to not have that name collision.
+   
+4) Fields in a subclass that have the same name as private fields in a superclass
+   represent a name collision, because the superclass method needs to call its private
+   field even if invoked from a subclass. The solution was to modify the subclass field
+   name using one or more prepended $.
+   
+5) Use of Class.getDeclaredMethods() reflection will return Method objects having the transpiled 
+   name, not the Java name. This could require some j2sNative adjustment 
+   to strip the $... parameters from the name if that is needed. 
+
+6) Use of Method.getParameterTypes() should work fine, provided class names
+   do not contain "_". This is because the transpiler converts "." to "_" when
+   creating the fully qualified JavaScript name.
+
+
+positive integers do not add to give negative numbers
+-----------------------------------------------------
+
+In Java, the following is true:
+
+  2000000000 + 2000000000 == -294967296
+
+But in SwingJS, that will be 4000000000. So, for example, the following
+strategy will fail in SwingJS:
+
+               int newLength = lineBuf.length * 2;
+               if (newLength < 0) {
+                       newLength = Integer.MAX_VALUE;
+               }
+
+"-1" in JavaScript is not 0xFFFFFFFF.
+
+And one must take care to not compare a negative number with a 32-bit mask. So
+
+(b & 0xFF000000) == 0xFF000000
+
+is true in Java for (int) b = -1, but is false in JavaScript, because 0xFF000000 is 4278190080, 
+while (-1 & 0xFF000000) is, strangely enough, -16777216, and, in fact, 
+
+(0xFF000000 & 0xFF000000) != 0xFF000000
+
+because -16777216 is not 4278190080.
+
+The fix is that one must compare similar operations:
+
+if ((b & 0xFF000000) == (0xFF000000 & 0xFF000000)) .....
+
+Importantly, the JavaScript Int32Array does behave properly. From 
+the Firefox developer console:
+
+>> x = new Int32Array(1)
+<- Int32Array(1) [ 0 ]
+>> x[0] = 4000000000
+<- 4000000000
+>> x[0]
+<- -294967296
+
+Notice that, perhaps unexpectedly, the following two constructs produce 
+different results in JavaScript:
+
+x = new Int32Array(1);
+b = x[0] = 4000000000;
+
+(b will be 4000000000)
+
+and
+
+x = new Int32Array(1);
+x[0] = 4000000000;
+b = x[0];
+
+(b will be -294967296)
+
+
+SwingJS leverages array typing to handle all byte and short arithmetic so as
+to ensure that any byte or short operation in JavaScript does give the same 
+result in Java. The design decision to not also do this with integer math was
+a trade-off between performance and handling edge cases.
+
+
+ArrayIndexOutOfBounds
+---------------------
+
+You cannot implicitly throw an ArrayIndexOutOfBoundsException in JavaScript.
+JavaScript will simply return "undefined", not throw an Exception. So:
+
+boolean notAGoodIdeaIsOutOfBounds(String[] sa, int i) {
+  try {
+     return (sa[i] == sa[i]);
+  } catch (ArrayIndexOutOfBoundsException e) {
+       return false;
+  }
+}
+
+will work in Java but not in JavaScript. Code should not depend upon this sort 
+of trap anyway, if you ask me. 
+
+Throwable vs Error vs Exception
+-------------------------------
+
+True JavaScript errors are trapped as Throwable, whereas you can still trap
+Error and Exception as well. So if you want to be sure to catch any JavaScript
+error, use try{}catch (Throwable t){}, not try{}catch (Exception e){}. 
+
+j
+ava.awt.Color
+--------------
+
+ColorSpace: only "support" CS_sRGB.
+
+ TODO -- any volunteers??
+
+javax.swing.JFileDialog
+-----------------------
+
+HTML5 cannot expose a file reading directory structure. But you certainly 
+can still do file reading and writing. It just works a little differently.
+It's a simple modification:
+
+               b = new JButton("FileOpenDialog");
+               b.addActionListener(new ActionListener() {
+
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               JFileChooser fc = new JFileChooser();
+                               Test_Dialog.this.onDialogReturn(fc.showOpenDialog(Test_Dialog.this));
+                               // Java will wait until the dialog is closed, then enter the onDialogReturn method.
+                               // JavaScript will exit with NaN immediately, and then call back with its actual value
+                               // asynchronously.
+                       }
+
+               });
+       
+               public void onDialogReturn(int value) {
+                       if (value != Math.floor(value))
+                               return; // in JavaScript, this will be NaN, indicating the dialog has been opened
+                       // If we are here, the dialog has closed, in both Java and JavaScript.
+                       System.out.println("int value is " + value);
+               }
+
+
+       @Override
+       public void propertyChange(PropertyChangeEvent event) {
+               Object val = event.getNewValue();
+               String name = event.getPropertyName();
+               System.out.println(name);
+               switch (event.getSource().getClass().getName()) {
+               case "javax.swing.JOptionPane":
+                       switch (name) {
+                       case "inputValue":
+                               onDialogReturn(val);
+                               return;
+                       case "value":
+                               if (val instanceof Integer)
+                                       onDialogReturn(((Integer) val).intValue());
+                               else
+                                       onDialogReturn(val);
+                               return;
+                       }
+                       break;
+               case "javax.swing.ColorChooserDialog":
+                       switch (name) {
+                       case "SelectedColor":
+                               onDialogReturn(val);
+                               return;
+                       }
+                       break;
+               case "javax.swing.JFileChooser":
+                       switch (name) {
+                       case "SelectedFile":
+                               File file = (File) val;
+                               byte[] array = (val == null ? null : /** @j2sNative file.秘bytes || */
+                                               null);
+                               onDialogReturn("fileName is '" + file.getName() + "'\n\n" + new String(array));
+                               return;
+                       }
+                       break;
+               }
+               System.out.println(
+                               event.getSource().getClass().getName() + " " + event.getPropertyName() + ": " + event.getNewValue());
+       }
+
+
+Developers are encouraged to create a separate class that handles general calls to JFileDialog. 
+An example class can be found in the SwingJS distribution as 
+
+/sources/net.sf.j2s.java.core/src/javajs/async/AsyncFileChooser.java.
+
+
+javax.swing.JOptionPane dialogs
+-------------------------------
+
+For this action to work, the parentComponent must implement
+propertyChangeListener, and any call to JOptionPanel should allow for
+an asynchronous response, meaning that there is no actionable code following the
+call to the dialog opening. 
+
+In addition, for compatibility with the Java version, implementation should
+wrap the call to getConfirmDialog or getOptionDialog in a method call to
+handle the Java:
+
+onDialogReturn(JOptionPane.showConfirmDialog(parentFrame,
+messageOrMessagePanel, "title", JOptionPane.OK_CANCEL_OPTION));
+
+Then parentFrame.propertyChange(event) should also call onDialogReturn.
+
+This will then work in both Java and JavaScript.
+
+Note that there is an int and an Object version of onDialogReturn().
+
+
+In JavaScript:
+
+The initial return from JOptionPane.showConfirmDialog and showMessageDialog
+will be (SwingJS) JDialog.ASYNCHRONOUS_INTEGER (NaN), testable as an impossible 
+Java int value using ret != -(-ret) if the parent implements PropertyChangeListener, or -1
+(CLOSE_OPTION) if not.
+
+For showOptionDialog (which returns Object) or showInputDialog (which returns
+String), the initial return will be (SwingJS) JDialog.ASYNCHRONOUS_OBJECT, testable as
+((Object) ret) instanceof javax.swing.plaf.UIResource if the parent implements
+PropertyChangeListeneer, or null if not.
+
+The second return will be the desired return.
+
+In Java:
+
+The initial return will be the one and only modal final return.
+
+
+
+For full compatibility, The calling method must not continue beyond this
+call.
+
+All of the standard Java events associated with Components are also
+available.
+
+Certain fall back mechanisms are possible, where onReturn does not exist, but
+only for the following cases:
+
+
+For showMessageDialog, for WARNING_MESSAGE and ERROR_MESSAGE, a simple
+JavaScript alert() is used, returning 0 (OK_OPTION) or -1 (CLOSED_OPTION).
+
+For showInputDialog, if the message is a string, a simple JavaScript prompt()
+with input box is used, returning the entered string or null.
+
+For showConfirmDialog, a simple JavaScript confirm() is used, in which case:
+
+for YES_NO_OPTION: YES_OPTION or NO_OPTION
+
+for YES_NO_CANCEL_OPTION: YES_OPTION or CANCEL_OPTION
+
+for OK_CANCEL_OPTION or any other: OK_OPTION or CANCEL_OPTION
+
+Note that you should implement a response for CLOSED_OPTION for
+showConfirmDialog. For other dialogs, a null return indicates the dialog was
+closed, just as for Java.
+
+Developers are encouraged to create a separate class that handles general calls. 
+An example class can be found in the SwingJS distribution as src/javajs/async/AsyncDialog.java.
+Very simple modifications to the Java allows asynchronous operation using AsyncDialog. Here
+is a simple "do you want to close this frame" example, where you can see that what we have
+done is to set the reply into an ActionListener that is defined in the constructor of 
+the AsyncDisplay object:
+
+// Original:
+//
+//     private void promptQuit() {
+//             int sel = JOptionPane.showConfirmDialog(null, PROMPT_EXIT, NAME, JOptionPane.YES_NO_OPTION);
+//             switch (sel) {
+//             case JOptionPane.YES_OPTION:
+//                     resultsTab.clean();
+//                     seqs.dispose();
+//                     if (fromMain) {
+//                             System.exit(0);
+//                     }
+//                     break;
+//             }
+//     }
+
+       private void promptQuitAsync() {
+               new AsyncDialog(new ActionListener() {
+
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                           int sel = ((AsyncDialog)e.getSource()).getOption();
+                               switch (sel) {
+                               case JOptionPane.YES_OPTION:
+                                       resultsTab.clean();
+                                       seqs.dispose();
+                                       if (fromMain) {
+                                               System.exit(0);
+                                       }
+                                       break;
+                               }
+                       }}).showConfirmDialog(null, PROMPT_EXIT, NAME, JOptionPane.YES_NO_OPTION);
+       }
+
+Very simple! 
+
+
+native methods
+--------------
+
+The J2S compiler ignores all static native method declarations.
+Anything of this nature needs to be implemented in JavaScript if it is needed,
+using j2sNative blocks:
+
+/**
+ * @j2sNative
+ *
+ *    var putYourJavaScriptCodeHere
+ *
+ */
+ Note that if you follow that directly with a {...} block, then 
+ the javadoc code will run in JavaScript, and the {...} code will run in Java.
+key Focus
+---------
+
+As of June, 2019, the keyboard focus manager is fully implemented. 
+The one catch is that JTextPane and JTextArea, which already consume
+VK_TAB in Java, cannot use CTRL-TAB to continue a tabbing cycle around
+the components in a window. Instead, CTRL-TAB is absorbed by the browser. 
+
+
+LookAndFeel and UI Classes
+--------------------------
+
+SwingJS implements the native browser look and feel as swingjs.plaf.HTML5LookAndFeel. 
+There are small differences between all look and feels -- MacOS, Windows, SwingJS.
+
+Expert developers know how to coerce changes in the UI by subclassing the UI for a 
+component. This probably will not work in SwingJS. 
+
+Note that LookAndFeel in Java usually determines canvas size in a Frame because 
+different operating systems (Mac OS vs Windows vs HTML5) will have 
+different edge sizes on their frames. If you want to ensure a component size, 
+use getContentPane().setPreferredSize().
+
+
+System.exit(0) does not stop all processes
+------------------------------------------
+
+Although System.ext(int) has been implemented in JavaScript, it just closes the 
+frames, stops all pending javax.swing.Timer objects in the queue, and runs any 
+threads added using Runtime.getRuntime().addShutdownHook(Thread).
+It may not stop all "threads." So don't rely on that.
+Applications are responsible for shutting down prior to executing System.exit(0). 
+
+
+myClass.getField not implemented
+--------------------------------
+
+java.lang.reflect.Field is implemented minimally. It is not
+certain that Field.getDeclaringClass() will work. If you just want a 
+value of a field, you can do this:
+
+/**
+ *@j2sNative
+ *
+ * return myClass[name]
+ */   
+
+But that is not a java.lang.reflection.Field object.
+
+
+"window" and other reserved JavaScript names
+--------------------------------------------
+
+No reserved top-level JavaScript name is allowed for a package name. So, for example, 
+one must rename packages such as "window" or "document" to names such as "win" or "doc".
+
+reserved field and method names
+-------------------------------
+
+In order to minimize the chance of added SwingJS field and method names colliding with ones 
+developers might use in subclassing Java classes, we have added U+79D8 (first character of Mandarin 
+"secret") to the characters already disrecommended by Java documentation ("$" and "_"). The only problem
+would be if you use that character followed by certain English words in certain classes. For example
+\u79D8canvas for JComponents (in java.awt.JSComponent) and \u79D8byte (in java.io.File).
+
+qualified field and method names
+--------------------------------
+
+Method names in SwingJS are fully qualified, meaning two methods with the same Java name but different
+parameters, such as write(int) and write(double), must not have the same name in JavaScript. (In this
+case, we will have write$I and write$D.) However, in certain cases it may be desirable to leave the
+method names unqualified. In particular, when an interface actually represents a JavaScript object, 
+the transpiler can leave a method name unqualified. The default situation for this is a class name 
+includes ".api.js" (case-sensitive). This means that any method in any class in a package js within 
+a package api, or any private interface js that has an outer interface api, will have all-unqualified
+methods. An example of this is swingjs.plaf.JSComboPopupList, which needs to communicate with a jQuery 
+object directly using the following interface:
+
+       private interface api {
+
+               interface js extends JQueryObject {
+
+                       abstract js j2sCB(Object options);
+
+                       abstract Object[] j2sCB(String method);
+
+                       abstract Object[] j2sCB(String method, Object o);
+
+                       abstract Object[] j2sCB(String method, int i);
+
+                       abstract int j2sCB(String OPTION, String name);
+
+               }
+       }
+
+Notice that all these variants of j2sCB() will call the same method in JavaScript by design.
+
+
+missing Math methods
+--------------------
+
+java.lang.Math is worked out, but some methods are missing, either because they
+involve long integer value that are inaccessible in JavaScript, or because I just
+didn't implement them. This is a result of continued Java development. 
+It is easy enough to add these methods if you have the source. They go into j2sClazz.js, 
+which is combined with other initial libraries into swingjs2.js by build_site.xml
+
+
+Component.getGraphics(), Graphics.dispose()
+-------------------------------------------
+
+Use of component.getGraphics() is discouraged in Java and in SwingJS. 
+Specifically in SwingJS, any call to component.getGraphics() or 
+BufferedImage.createGraphics() or Graphics.create(...) should be matched with graphics.dispose(), 
+particularly when it is called outside the context of a paint(Graphics)
+call from the system. 
+
+If you see your graphics scrolling down the page with each repaint, 
+look for where you have used Component.getGraphics() and not Graphics.dispose().
+For example, this will definitely NOT work in SwingJS:
+
+  this.paint(getGraphics())
+  
+and really should not work in Java, either, as it is technically a resource memory leak.
+
+Instead, if you really do not want to use repaint(), use this:
+
+  Graphics g = getGraphics();
+  paint(g);
+  g.dispose();
+
+
+
+Graphics.setClip()
+------------------
+
+The HTML5 canvas.clip() method is permanent. You can only reset the clip using
+save/restore. This is different from Java, where you can temporarily change it using
+
+  Shape oldClip = Graphics.getClip();
+  Graphics.setClip(newClip);
+   ...
+  Graphics.setClip(oldClip); 
+
+If you need to do something like this, you must schedule the paints
+to not have overlapping clip needs.
+
+
+MAJOR ISSUES--for Bob and Udo within SwingJS
+============================================
+
+fonts
+-----
+
+Fonts and FontMetrics will all be handled in JavaScript. Font matching will 
+not be exact, and composite (drawn) fonts will not be supported. 
+
+SwingJS handles calls such as font.getFontMetrics(g).stringWidth("xxx") by 
+creating a <div> containing that text, placing it in an obscure location on 
+the page, and reading div.getBoundingClientRect(). This is a VERY precise
+value, but can be a pixel or two off from what Java reports for the same font.
+OS-dependent classes
+--------------------
+
+Static classes such as:
+
+   java.awt.Toolkit
+   java.awt.GraphicsEnvironment
+   
+   
+which are created using Class.forName are implemented using classes in the swingjs package.
+
+AWTAccessor is not implemented. 
+
+   
+AWT component peers and component "ui" user interfaces
+------------------------------------------------------
+
+ComponentPeer is a class that represents a native AWT component.
+Components with such peers are called "heavy-weight" components.
+They are expected to do the dirty work of graphics drawing. 
+
+Java Swing implements peers only for JApplet, JDialog, JFrame, and JWindow. 
+References to such objects have been removed, but clearly there must be 
+some connection to similar DOM objects, even for "light-weight" components. 
+
+
+  
+MAJOR ISSUES--to be resolved by implementers
+============================================
+
+fonts
+-----
+
+Glyph/composite/outline fonts are not supported.
+   
+
+
+threads
+-------
+
+Thread locking and synchronization are not relevant to JavaScript.
+Thus, anything requiring "notify.." or "waitFor.." could be a serious issue.
+All threading must be "faked" in JavaScript. Specifically not available is:
+
+  Thread.sleep()
+  
+javax.swing.AbstractButton#doClick(pressTime) will not work, as it requires Thread.sleep();
+    
+However, java.lang.Thread itself is implemented and used extensively. 
+
+Methods thread.start() and thread.run() both work fine. 
+
+For simple applications that use Thread.sleep() just to have a delay, as in a frame rate, for 
+example, one can use javax.swing.Timer instead. That is fully implemented. 
+
+Likewise, java.util.Timer can be replaced with no loss of performance with javax.Swing.Timer.
+Note that java.util.TimerTask is implemented, but it can also be replaced by an implementation of Runnable.
+
+task = new TimerTask(){....};
+t = new java.util.Timer();
+t.schedule(task, 0, 1);
+
+becomes
+
+task = new TimerTask(){....}; // or task = new Runnable() {...}
+t = new javax.swing.Timer(1, new ActionListener() {
+       @Override
+       public void actionPerformed(ActionEvent e) {
+               task.run();
+       }
+};
+t.setInitialDelay(0); // not particularly necessary
+t.start();
+
+In addition, SwingJS provides swingjs.JSThread, which can be subclassed
+if desired. This class allows simple 
+
+  while(!interrupted()){
+       wait()
+       ...
+  }  
+
+action through an asynchronous function run1(mode). For example:
+
+       protected void run1(int mode) {
+               try {
+                       while (true)
+                               switch (mode) {
+                               case INIT:
+                                       // once-through stuff here
+                                       mode = LOOP;
+                                       break;
+                               case LOOP:
+                                       if (!doDispatch || isInterrupted()) {
+                                               mode = DONE;
+                                       } else {
+                                               Runnable r = new Runnable() {
+                                                       public void run() {
+                                                               // put the loop code here
+                                                       }
+                                               };
+                                               dispatchAndReturn(r);
+                                               if (isJS)
+                                                       return;
+                                       }
+                                       break;
+                               // add more cases as needed
+                               case DONE:
+                                       // finish up here
+                                       if (isInterrupted())
+                                               return;
+                                       // or here
+                                       break;
+                               }
+               } finally {
+                       // stuff here to be executed after each loop in JS or at the end in Java
+               }
+       }
+
+image loading
+-------------
+- All image loading in SwingJS is synchronous. A MediaTracker call will immediately return "complete".
+  However, it still may take one system clock tick to fully load images. Thus, it is recommended that
+  images be preloaded in the static block of the applet if it is necessary that they be available in init().
+  This is only an issue if you are trying to access the pixel buffer of the image in JavaScript. 
+  
+- Applet.getImage(path, name) will return null if the image does not exist. 
+
+- BufferedImage: only "support" imageType RGB and ARGB
+
+  -BH: This is a temporary edit, just to get us started. Certainly GRAY will be needed
+
+
+BigInteger and BigDecimal
+-------------------------
+
+java.math.BigInteger is fully supported; java.math.BigDecimal is roughed 
+in and not fully tested (07/2019). 
+
+Both classes present significant issues for JavaScript, as they are based in 
+Java's 64-bit long for all their operations. Here is the JavaDoc note I added
+to BigInteger:
+
+ * SwingJS note: Because of the limitations of JavaScript with regard
+ * to long-integer bit storage as a double, this implementation drops
+ * the integer storage bit length to 24, giving 48 for long and leaving
+ * the last 16 bits clear for the exponent of the double number. This should
+ * not affect performance significantly. It does increase the storage 
+ * size by about 33%. By bringing an "int" to 3 bytes, we can easily construct
+ * and use byte[] data intended for the original BitSet.  
+
+"Easily" may be a bit strong there. This was a serious challenge.
+
+BigDecimal seems to run normally, but in order to do that, my hack involves
+reducing the size of an integer that is allowed to be stored as such and not
+in byte[] as a BigInteger. I'm sure there is a performance hit, but it does work.
+
+no format internationalization
+------------------------------
+
+For now, just en for number and date formatters
+
+no winding rules
+----------------
+
+  When filling a graphic, only nonzero winding rule is implemented in HTML5 Canvas2D.
+
+
+
+text-related field implementation
+---------------------------------
+
+Text fields are:
+
+JTextField   (JavaScript <input type="text">)
+JTextArea    (JavaScript <textarea>)
+JTextPane    (JavaScript <div>)
+JEditorPane  (JavaScript <div>)
+
+For the initial implementation, we don't implement infinite undo/redo, and the abstract 
+document model is much less elaborate. Only PlainDocument (in the form of JSPlainDocument)
+is implemented. The Document returned by JTextField.getDocument() is a javax.swing.text.Document.
+
+All scrolling is handled by HTML5. javax.swing.AutoScroller is not implemented.
+public static methods .stop, .isRunning, .processMouseDragged require true Java threading
+and so are not implmented. javax.swing.text.View and its subclasses are not implemented. 
+
+The JS document model does not allow two text fields to address the same underlying document. 
+
+
+Formatter/Regex limitations
+---------------------------
+
+Some browsers cannot process Regex "look-behind" process such as (?<=\W)
+java.util.regex.Matcher and Pattern use JavaScript's RegExp object rather than
+the native Java object. These are not identical. Only flags /igm are supported.
+Matcher.start(groupID) is not supported.
+
+java.util.Formatter will function correctly for all standard %... patterns.
+
+integer 1/0 == Infinity
+-----------------------
+
+1/0 in Java throws "java.lang.ArithmeticException: / by zero", but in JavaScript is just Infinity. 
+
+
+Summary
+-------
+
+These are all the known limitations of SwingJS. We have not found any of these limitations
+to be show-stoppers. The primary issue for newcomers to SwingJS is having the source code.
+You must check that source code for all your library jar files is available or, if you
+choose, you will need to decompile those classes. We have used decompilation on some projects,
+and it works just fine. So, technically, all we really need are JAR/class files. But the 
+source is by far superior. It's generally prettier, and it has the license information that
+may or may not be present with the JAR or class files. Use class files at your own risk.
+
+Bob Hanson
+2019.08.16
+
+
+Additional Issues
+-----------------
+
+Annotation is working for classes, methods, and fields (12/22/19). Method reflection, however,
+is limited. Interfaces do not expose their methods, as the transpiler does not actually transpile
+the interfaces themselves. And method reflection only includes annotated methods.
+
+java.util.concurrent is not fully elaborated. This package is rewritten to not actually use the
+memory handling capabilities of concurrency, which JavaScript does not have access to.
+
+System.getProperties() just returns a minimal set of properties.
+
+
index d5e28c1..5e9828e 100644 (file)
Binary files a/swingjs/ver/3.2.9/SwingJS-site.zip and b/swingjs/ver/3.2.9/SwingJS-site.zip differ
diff --git a/swingjs/ver/3.2.9/differences.txt b/swingjs/ver/3.2.9/differences.txt
new file mode 100644 (file)
index 0000000..70eabbc
--- /dev/null
@@ -0,0 +1,1480 @@
+Notes
+=====
+
+---IMPORTANT CHARACTER SET NOTE---
+
+It is critical that all development work in Java2Script 
+be done in UTF-8. This means:
+
+- making sure your Eclipse project is set up for UTF-8 (not the Eclipse default?)
+- making sure your server can serve up UTF-8 by default for any browser-loaded files
+- making sure you don't edit a Java2Script class file or one of the site .js files
+    using a non-UTF-8 editor. It may replace non-Latin characters with "?" or garbage.
+- making sure that your web pages are delivered with proper headings indicating HTML5 and UTF-8
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+
+Note that the DOCTYPE tag is critical for some browsers to switch into HTML5 mode. (MSIE?)
+
+
+
+  
+In particular, the Mandarin character ç§˜ (mi; "secret") is used extensively throughout
+the SwingJS class files to distinguish j2s-specific fields and methods that must not 
+ever be shadowed or overridden by subclasses. For example, we see in java.lang.Thread.java:
+
+               public static JSThread ç§˜thisThread;
+
+----------------------------------
+
+
+updated 3/21/2020 -- adds note about HashMap, Hashtable, and HashSet iterator ordering
+updated 3/20/2020 -- adds note about interning, new String("xxx"), and "xxx"
+updated 2/26/2020 -- adds Graphics.setClip issue
+updated 12/22/19 -- additional issues
+updated 11/03/19 -- adds information about File.exists() and points to src/javajs/async
+updated 10/26/19 -- adds information about File.createTempFile()
+updated 8/16/19 -- minor typos and added summary paragraph
+updated 7/19/19 -- clarification that AWT and Swing classes are supported directly
+updated 5/13/19 -- Mandarin U+79D8 reserved character; Missing Math methods; int and long
+updated 5/10/19 -- adds a section on static issues in multi-(duplicate)-applet pages
+updated 1/4/19 -- nio
+updated 9/15/18 -- adds integer 1/0 == Infinity
+updated 7/24/18 -- most classes replaced with https://github.com/frohoff/jdk8u-jdk
+updated 6/5/17 -- reserved package name "window"
+updated 3/11/17 -- myClass.getField
+updated 3/7/17 -- overloading of JSplitPane.setDividerLocation
+updated 3/2/17 -- more indication of classes not implemented (KeyListener)
+
+=============================================================================
+SwingJS and OpenJDK 8+
+=============================================================================
+
+SwingJS implements a wide range of the Java language in JavaScript. The base
+version for this implementation is OpenJDK8. some classes are implemented using 
+older source code, and there are some missing methods. For the most part, this is 
+no real problem. You can add or modify any java class just be adding it as source
+in your project. Or (preferably) you can contact me, and I can get it into the 
+distribution. Or (even more preferably) you can do that via a patch submission. 
+
+=================
+DESIGN PHILOSOPHY
+=================
+
+The java2script/SwingJS design goal is to recreate a recognizable, easily debuggable
+equivalent in JavaScript for as much of Java as practical. This means, for example, 
+that one can call in JavaScript 
+
+  new java.util.Hashtable()
+  
+and for all practical purposes it will appear that Java is running.
+
+
+Method and Field Disambiguation
+-------------------------------
+
+SwingJS has no problem with the overloading of methods, for example:
+
+  public void print(int b);
+  public void print(float b);
+
+JavaScript does not allow overloading of methods, and the common practice in
+Java of naming a field the same as a method -- isAllowed and isAllowed() -- is
+not possible in JavaScript. As a result, SwingJS implements "fully-qualified" 
+method names using "$" parameter type separation. Thus, these methods in SwingJS
+will be referred to as print$I and print$F. The rules for this encoding are
+relatively simple: 
+
+1. The seven primitive types in Java are encoded $I (int), $L (long), $F (float), 
+$D (double), $B (byte) $Z (boolean), and $H (short). 
+
+2. String and Object are encoded as $S and $O, respectively.
+
+3. "java_lang_" is dropped for all other classes in the java.lang package (as in Java).
+   For example:  $StringBuffer, not $java_lang_StringBuffer
+
+4. All other classes are encoded as 
+
+ "$" + Class.getName().replace(".","_")
+
+For example, in Java we see:
+
+  public void equals(Object o) {...}
+
+Whereas in SwingJS we have:
+
+  Clazz.newMeth(C$, 'equals$O', function (o) {...}
+
+And 
+
+ this.getContentPane().add(bar, "North");
+
+becomes
+
+ this.getContentPane$().add$java_awt_Component$O(bar, "North");
+
+5. Arrays are indicated with appended "A" for each level. So
+
+  setDataVector(Object[][] dataVector, Object[] columnIdentifiers)
+  
+becomes
+
+  setDataVector$OAA$OA(dataVector, columnIdentifiers)
+
+(It is recognized that this design does introduce a bit of ambiguity, in that
+ in principal there could be user class named XA and X in the same package,
+ and methods a(X[]) and a(XA) in the same class that cannot be distinguished.
+ The benefit of this simple system, however, triumphed over the unlikelyhood
+ of that scenario.) The transpiler could be set to flag this possibility.
+
+6. Constructors are prepended with "c$". So 
+
+  public JLabel(String text) {...}
+  
+becomes:
+
+  Clazz.newMeth(C$, 'c$$S', function (text) {...});
+
+Field disambiguation involves prepending. In Java, a class and its subclass 
+can both have the same field name, such as 
+
+ boolean visible;
+When this happens, it is called "shadowing", and though not recommended, Java allows
+it. The Java2Script transpiler will prepend such shadowing fields with "$" so that the
+subclass instance has both "visible" (for use in its methods inherited from its
+superclass) and "$visible" (for its own methods). Thus, we might see in Java:
+
+  this.visible = super.visible;
+  
+while in SwingJS we will see:
+
+  this.$visible=this.visible;
+
+since JavaScript does not have the "super" keyword.
+
+
+
+Parameterless methods such as toString() are appended with "$" to become toString$().
+The one exception to this rule is private methods, which are saved in (truly) private 
+array in the class (and are not accessible by reflection). Private parameterless 
+methods retain their simple Java name, since they cannot conflict with field names.
+
+This renaming of methods has a few consequences, which are discussed more fully below.
+See particularly the section on "qualified field and method names", where it is described
+how you can use packages or classes or interfaces with ".api.js" in them to represent JavaScript
+objects for which all method names are to be left unqualified. Note that it is not 
+possible to cherry-pick methods to be unqualified; only full packages, classes or 
+interfaces can hold this status.
+
+The swingjs.api.js package in particular contains a number of useful interfaces that
+you can import into your project for JavaScript-specific capabilities.
+
+
+Applet vs. Application
+----------------------
+
+One of the very cool aspects of SwingJS is that it doesn't particularly matter if a browser-based
+Java app is an "applet" or an "application". We don't need JNLP (Java Network Launch Protocol) 
+because now we can just start up any Java application in a browser just as easily as any applet.
+The associative array that passes information to the SwingJS applet (information that formerly
+might have been part of the APPLET tag, such as width, height, and codebase, always referred to 
+in our writing as "the Info array") allows the option to specify the JApplet/Applet "code" 
+class or the application "main" class. Either one will run just fine.
+
+
+Performance
+-----------
+
+Obviously, there are limitations. One is performance, but we have seen reproducible 
+performance at 1/6 - 1/3 the speed of Java. Achieving this performance may require
+some refactoring of the Java to make it more efficient in both Java and JavaScript. 
+"for" loops need to be more carefully crafted; use of "new" and "instanceof" need to be
+minimized in critical areas. Note that method overloading -- that is, the same method name
+with different parameters, such as read(int) and read(byte) -- is no longer any problem. 
+  
+
+Threads
+-------
+
+Although there is only a single thread in JavaScript, meaning Thread.wait(), Thread.sleep(int) and 
+Thread.notify() cannot be reproduced, we have found that this is not a serious limitation. 
+For example, javax.swing.Timer() works perfectly in JavaScript. All it means is that threads 
+that use sleep(int) or notify() must be refactored to allow Timer-like callbacks. That is, 
+they must allow full exit and re-entry of Thread.run(), not the typical while/sleep motif. 
+
+The key is to create a state-based run() that can be exited and re-entered in JavaScript.
+
+
+Static fields
+-------------
+
+Final static primitive "constant" fields (String, boolean, int, etc.) such as 
+
+static final int TEST = 3;
+static final String MY_STRING = "my " + "string";
+
+are converted to their primitive form automatically by the Eclipse Java compiler 
+and do not appear in the JavaScript by their names. 
+
+Other static fields are properties of their class and can be used as expected.
+
+Note, however, that SwingJS runs all "Java" code on a page in a common "jvm" 
+(like older versions of Java). So, like the older Java schema, the JavaScript 
+equivalents of both applets and applications will share all of their static 
+fields and methods. This includes java.lang.System. 
+
+Basically, SwingJS implementations of Java run in a browser page-based sandbox 
+instead of an applet-specific one.
+
+In general, this is no problem. But if we are to implement pages with 
+multiple applets present, we must be sure to only have static references 
+that are "final" or specifically meant to be shared in a JavaScript 
+environment only (since they will not be shared in Java).
+
+A simple solution, if static non-constant references are needed, is to attach the 
+field to Thread.currentThread.threadGroup(), which is an applet-specific reference.
+Be sure, if you do this, that you use explicit setters and getters:
+
+For example, 
+
+private static String myvar;
+
+...
+
+public void setMyVar(String x) {
+  ThreadGroup g = Thread.currentThread().threadGroup();
+  /**
+   * @j2sNative g._myvar = x;
+   * 
+   */
+   {
+     myvar = x;
+   }
+}
+
+public String getMyVar() {
+  ThreadGroup g = Thread.currentThread().threadGroup();
+  /**
+   * @j2sNative return g._myvar || null;
+   * 
+   */
+   {
+     return myvar;
+   }
+}
+ in Java will get and set x the same in JavaScript and in Java. 
+A convenient way to do this in general is to supply a singleton class with
+explicitly private-only constructors and then refer to it in Java and in JavaScript
+instead of using static field, referring to myclass.getIntance().xxx instead of 
+myclass.xxx in Java (and JavaScript). 
+
+This was done extensively in the Jalview project. See jalview.bin.Instance.
+
+
+Helper Packages -- swingjs/ and javajs/
+---------------------------------------
+
+The SwingJS library is the swingjs/ package. There are interfaces that may be of assistance
+in swingjs/api, but other than that, it is not recommended that developers access classes in 
+this package. The "public" nature of their methods is really an internal necessity.
+
+In addition to swingjs/, though, there are several useful classes in the javajs/ package
+that could be very useful. This package is a stand-alone package that can be 
+cloned in any Java project that also would be great to have in any JavaScript project
+-- SwingJS-related or not. Functionality ranges from reading and writing various file 
+formats, including PDF, BMP, PNG, GIF, JPG, JSON, ZIP, and CompoundDocument formats.
+
+A variety of highly efficient three- and four-dimensional point, vector, matrix, and 
+quaternion classes are included, as they were developed for JSmol and inherited from that
+project. 
+
+Of particular interest should be javajs/async/, which includes
+
+javajs.async.Async
+javajs.async.AsyncColorChooser
+javajs.async.AsyncDialog
+javajs.async.AsyncFileChooser
+
+See javajs.async.Async JavaDoc comments for a full description of 
+these useful classes.
+
+
+Modal Dialogs
+-------------
+
+Although true modal dialogs are not possible with only one thread, a functional equivalent -- 
+asynchronous modal dialogs -- is relatively easy to set up. All the JOptionPane dialogs will
+return PropertyChangeEvents to signal that they have been disposed of and containing the results. 
+See below and classes in the javajs.async package.
+
+
+Native calls
+------------
+
+Native calls in Java are calls to operating system methods that are not in Java. JavaScript
+has no access to these, of course, and they must all be replaced by JavaScript equivalents.
+Fortunately, they are not common, and those that are present in Java (for example, in calculating
+checksums in ZIP file creation) are at a low enough level that most developers do not utilize them
+or do not even have access to them. All native calls in Java classes have been replaced by 
+Java equivalents.
+
+
+Swing GUI Peers and UIClasses
+-----------------------------
+
+One of the biggest adaptations introduced in SwingJS is in the area of the graphical 
+user interface. The issue here is complex but workable. In Java there are two background 
+concepts -- the Component "peer" (one per "heavy-weight" component, such as a Frame) and the 
+component "uiClass" (one per component, such as JButton or JTextField).
+
+Peers are native objects of the operating system. These are the virtual buttons and text areas
+that the user is interacting with at a very base level. Their events are being passed on to 
+Java or the browser by the operating system. UI classes provide a consistent "look and feel" 
+for these native objects, rendering them onto the native window canvas and handling all 
+user-generated events. They paint the borders, the backgrounds, the highlights, of every 
+control you see in Java. There is one-to-one correspondence of Swing classes and UI classes. 
+Setting the Look and Feel for a project amounts to selecting the directory from which to draw 
+these UI classes. The UI classes can be found in the javax.swing.plaf ("platform look and feel") 
+package.
+
+Early on in the development of SwingJS, we decided not to fully reproduce the painfully detailed 
+bit-by-bit painting of controls as is done in Java. Instead, we felt it was wiser to utilize the standard
+HTML5 UI capabilities as much as possible, using DIV, and INPUT especially, with extensive use
+of CSS and sometimes jQuery (menus, and sliders, for example). Thus, we have created a new 
+set of UIs -- the "HTML5 Look and Feel". These classes can be found in swingjs.plaf. Besides being
+more adaptable, this approach allows far more versatility to SwingJS developers, allowing them
+to modify the GUI to suit their needs if desired.
+
+In SwingJS, since we have no access to native peers except through the browser DOM,
+it seemed logical to merge the peer and UI idea. So instead of having one peer per heavy-weight control and
+one UI class instance for each control type, we just have one UI class instance per control, and
+that UI class instance is what is being referred to when a "peer" is notified. 
+
+In some ways this is a throw back to when all of Swing's components were subclasses of
+specific AWT components such as Button and List. These "heavy-weight components" all had their 
+own individual native peers and thus automatically took on the look and feel provided by the OS. 
+Later Swing versions implemented full look and feel for all peers, leaving only JDialog, JFrame,
+and a few other classes to have native peers. But in SwingJS we have again a 1:1 map of component
+and UI class/peer instance.
+
+The origin of most issues (read "bugs") in relation to the GUI will probably be found in the
+swingjs.plaf JSxxxxUI.java code.
+
+  
+Swing-only Components -- no longer an issue
+-------------------------------------------
+
+Swing was introduced into Java well after the Java Abstract Window Toolkit (AWT) was well
+established. As such, its designers chose to allow AWT controls such as Button and List to be used 
+alongside their Swing counterparts JButton and JList. Reading the code, it is clear that this 
+design choice posed a huge headache for Swing class developers. 
+
+For SwingJS, we decided from the beginning NOT to allow this mixed-mode programming and 
+instead to require that all components be Swing components. 
+
+However, this is no longer an issue. All AWT components in SwingJS are now subclasses of 
+javax.swing.JComponent. So far, we have found no problem with this.
+
+The a2s Adapter Package
+-----------------------
+
+Originally, we thought that we would restrict ourselves to JApplets only. That is, only
+Swing-based applets. But as we worked, we discovered that there are a lot of great 
+applets out there that are pre-Swing pure-AWT java.applet.Applet applets. Our problem was 
+that we also wanted it to be possible to quickly adapt these applets to JavaScript as well.
+The solution turned out to be simple: Write a package (a2s) that recreates the interface for 
+non-Swing components as subclasses of Swing components. Thus, a2s.Button subclasses javax.swing.JButton
+but also accepts all of the methods of java.awt.Button. This works amazingly well, with a few
+special adaptations to the core javax.swing to be "AWT-aware." All AWT components now subclass 
+a2s components, which in turn subclass JComponents. So no changes in code are necessary. We have
+successfully transpiled over 500 applets using this strategy. (Kind of surprising, actually, that
+the original Java developers did not see that option. But we have a hindsight advantage here.)
+
+
+Working with Files
+==================
+
+Simple String file names are not optimal for passing information about
+read files within SwingJS applications. 
+All work with files should either use Path or File objects exclusively. 
+These objects, after a file is read or checked for existence, will already 
+contain the file byte[] data. Doing something like this:
+
+File f = File("./test.dat");
+boolean isOK = f.exists();
+
+will load f with its byte[] data, if the file exists. 
+
+But if after that, we use:
+
+File f2 = new File(f.getAbsolutePath());
+
+f2 will not contain that data. Such copying should be done as:
+
+File f2 = new File(f);
+
+in which case, the byte[] data will be transferred.
+
+
+SwingJS uses the following criteria to determine if File.exists() returns true:
+
+(1) if this File object has been used directly to read data, or 
+(2) if reading data using this File object is successful.
+
+Note that you cannot check to see if a file exists before input or if it 
+was actually written or if it already exists prior to writing in SwingJS.  
+
+Thus, you should check each use of file.exists() carefully, and if necessary, provide a J2sNative 
+block that gives an appropriate "OK" message, for example:
+
+(/** @j2sNative 1 ? false : */ outputfile.exits())
+
+or 
+
+(/** @j2sNative 1 ? true : */ inputfile.exits())
+
+Temporary files can be created in SwingJS. SwingJS will maintain a pseudo-filesystem for files 
+created with File.createTempFile(). This is useful in that closure of writing to a temporary file 
+does not generate a pseudo-download to the user's machine.
+
+
+UNIMPLEMENTED CLASSES BY DESIGN
+===============================
+
+The SwingJS implementation of the following classes are present 
+in a way that gracefully bypasses their functionality:
+
+accessibility
+security
+serialization
+
+
+
+TODO LIST FOR UNIMPLEMENTED CLASSES
+===================================
+
+JEditorPane (minimal implementation) - DONE 12/2018; some issues still
+JSplitPane - DONE 8/2018
+JTabbedPane - DONE 10/2018
+JTree - done 12/2019
+
+
+MINOR ISSUES--required some rewriting/refactoring by Bob and Udo  
+================================================================
+
+Thread.currentThread() == dispatchThread
+
+
+MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS  
+=====================================================================
+
+See below for a full discussion.
+
+HashMap, Hashtable, and HashSet iterator ordering
+interning, new String("xxx") vs "xxx"
+Names with "$" and "_"
+positive integers do not add to give negative numbers
+ArrayIndexOutOfBounds
+java.awt.Color
+native methods
+javax.swing.JFileDialog
+key focus
+LookAndFeel and UI Classes
+System.exit(0) does not stop all processes
+list cell renderers must be JComponents
+myClass.getField not implemented
+"window" and other reserved JavaScript names
+reserved field and method names
+qualified field and method names
+missing Math methods
+Component.getGraphics(), Graphics.dispose()
+Graphics.setClip()
+
+MAJOR ISSUES--for Bob and Udo within SwingJS
+============================================
+
+fonts
+OS-dependent classes
+AWT component peers
+some aspects of reflection
+
+MAJOR ISSUES--to be resolved by implementers
+============================================
+
+fonts
+threads
+modal dialogs
+image loading
+BigDecimal not fully implemented 
+no format internationalization
+no winding rules
+text-related field implementation
+Formatter/Regex limitations
+integer 1/0 == Infinity
+
+======================================================================== 
+
+DISCUSS
+=======
+
+Table row/col sorter needs checking after removal of java.text.Collator references
+
+I had to move all of SunHints class to RenderingHints, or the 
+two classes could not be loaded. Shouldn't be a problem, I think. The sun classes are
+not accessible to developers in Java anyway, since they are generally package private.
+
+========================================================================== 
+
+//////////////////////////////////////////////////////////////////////////////
+
+UNIMPLEMENTED CLASSES
+=====================
+
+accessibility
+-------------
+
+All Accessibility handling has been commented out to save the download footprint.
+This removes the need for sun.misc.SharedSecrets as well. 
+Nothing says we could not implement accessibility. We just didn't.
+
+
+security
+--------
+
+All JavaScript security is handled by the browser natively. 
+Thus, Java security checking is no longer necessary, and 
+java.security.AccessController has been simplified to work without
+native security checking.
+
+Note that private methods in a class are REALLY private. 
+
+
+serialization
+-------------
+
+All serialization has been removed. It was never very useful for Swing anyway, 
+because one needs exactly the same Java version to save and restore serialized objects.
+
+
+keyboard accelerators and mnemonics
+-----------------------------------
+
+This work was completed in the spring of 2019. Note that in a browser, some 
+key strokes, particularly CTRL-keys, are not available. Bummer.
+
+
+MINOR ISSUES--required some rewriting/refactoring by Bob and Udo  
+================================================================
+
+
+Thread.currentThread() == dispatchThread
+----------------------------------------
+
+changed to JSToolkit.isDispatchThread()
+
+
+MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS  
+=====================================================================
+
+HashMap, Hashtable, and HashSet iterator ordering
+-------------------------------------------------
+
+In Java, iterators for HashMap, Hashtable, and HashSet do not guarantee any particular order. 
+From the HashMap documentation for Java 8:
+
+       This class makes no guarantees as to the order of the map; in particular, it does not 
+       guarantee that the order will remain constant over time.
+Likewise, for HashSet (because it is simply a convenience method for HashMap<Object,PRESENT>:
+
+       [HashSet] makes no guarantees as to the iteration order of the set.
+
+JavaScript's Map object is different. It is basically a LinkedHashMap, so it guarantees iteration
+in order of object addition.
+
+Starting with java2script 3.2.9.v1, these classes use the JavaScript Map object rather than hash codes
+whenever all keys are strictly of JavaScript typeof "string". If any key is introduced that is not a string, the
+implementation falls back to using hash codes, the same as Java. 
+
+Note strings created using new String("xxxx") are NOT typeof "string"; they are typeof "object".
+
+The result is significantly faster performance (3-12 x faster) than originally, and up to 3 x faster
+performance in JavaScript than in Java itself. Right. Faster than Java. 
+
+The JavaScript Map implementation is implemented UNLESS the constructor used is the one that
+specifies both initial capacity and load factor in their constructor. Thus, 
+
+new Hashtable()
+new HashMap()
+new HashMap(16)
+new HashSet()
+
+all use the JavaScript Map. But
+
+new Hashtable(11, 0.75f)
+new HashMap(16, 0.75f)
+new HashSet(16, 0.75f)
+
+do not. 
+
+This design allows for opting out of the JavaScript Map use in order to retain the exact behavior of 
+iterators in JavaScript as in Java.
+
+
+interning, new String("xxx") vs "xxx"
+-------------------------------------
+
+Note that the following are true in JavaScript:
+
+typeof new String("xxxx") == "object"
+typeof "xxxx" == "string"
+var s = "x";typeof ("xxx" + s) == "string"
+
+There is no equivalence to this behavior in Java, where a String is a String is a String.
+
+Be aware that SwingJS does not always create a JavaScript String object using JavaScript's 
+new String(...) constructor. It only does this for Java new String("xxxx") or new String(new String()). 
+
+In all other cases, new String(...) (in Java) results in a simple "xxxx" string in JavaScript. 
+That is, it will be JavaScript typeof "string", not typeof "object". 
+
+The reason for this design is that several classes in the Java core use toString() 
+methods that return new String(), and those classes that do that would cause a JavaScript error
+if implicitly stringified if new String() returned a JavaScript String object. 
+
+This is fine in JavaScript
+
+test1 = function() { return { toString:function(){ return "OK" } } }
+"testing" + new test1()
+>> "testingOK"
+
+But for whatever reason in JavaScript:
+
+test2 = function() { return { toString:function(){ return new String("OK") } } }
+"testing" + new test2()
+>> Uncaught TypeError: Cannot convert object to primitive value
+
+The lesson here is never to use 
+
+  return new String("...");
+
+in a Java toString() method. In Java it will be fine; in JavaScript it will also be fine as long as
+that method is never called in JavaScript implicitly in the context of string concatenation.
+
+A note about interning. Consider the following six Java constructions, where we have a == "x";
+
+"xxx"
+"xx" + "x"
+new String("xxx").intern()
+
+new String("xxx")
+"xx" + a.toString()
+"xx" + a
+
+All six of these will return java.lang.String for .getClass().getName().
+However, the first three are String literals, while the last three are String objects. 
+Thus:
+        "xxx" == "xxx"
+        "xxx" == "xx" + "x"
+        "xxx" == new String("xxx").intern()
+
+but none of the other three are equivalent to "xxx" or each other:
+
+              "xxx" != new String("xxx")
+              "xxx" != "xx" + a.toString()
+              "xxx" != "xx" + a
+  new String("xxx") != new String("xxx") 
+           "xx" + a != new String("xxx") 
+
+etc.
+
+As in Java, in SwingJS, all of the following Java assertions pass as true:
+
+               assert("xxx" == "xx" + "x"); 
+               assert("xxx" == ("xx" + a).intern()); 
+               assert("xxx" === new String("xxx").intern()); 
+               
+and both of these do as well:
+
+               assert(new String("xxx") != "xxx"); 
+               assert(new String("xxx") != new String("xxx")); 
+
+But the following two fail to assert true:
+
+        assert("xxx" != "xx" + a);
+        assert("xxx" != "xx" + a.toString());
+
+because in JavaScript, both of these right-side expressions evaluate to a simple "interned" string.
+
+In Java, however, these assertions are true because Java implicitly "boxes" String 
+concatentaion as a String object, not a literal. 
+
+Most of us know not to generally use == with Strings unless they are explicitly interned. 
+Where this problem may arise, though, is in IdentityHashMap, which compares objects using 
+System.identityHashCode(), which is not the same for different objects or their string literal equivalents.
+
+My recommendation, if you need to use IdentityHashMap with strings is to always use an explicit String.intern()
+for any keys -- unless you really want to keep every string as separate keys even if they are the same sequence, 
+in which case, use new String(). This will work in Java and in  JavaScript.
+
+Be aware when working with strings that come from SwingJS and are being used by other JavaScript modules
+that those that are String objects will return "object" for the JavaScript typeof operator, not "string".
+
+The easy way to ensure this is no problem is to concatenate strings with "" to force immediate interning:
+
+  var x = aJavaObject.getString() + "";
+
+unless you are certain that the string is being returned is a raw JavaScript string.   
+
+Names with "$" and "_"
+----------------------
+
+For the most part, this should be no problem. 
+
+Note that the use of $ and _ in Java field names has always been discouraged:
+[https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html]
+
+       You may find some situations where auto-generated names will contain the dollar sign, 
+       but your variable names should always avoid using it. A similar convention 
+       exists for the underscore character; while it's technically legal to begin your 
+       variable's name with "_", this practice is discouraged.
+
+Some impacts of transpiling method names with full qualification:
+
+1) SwingJS will introduce fields that start with $ or _. These will not conflict
+   if the above convention is followed.
+   
+2) Fields that have the same Java name as a method are not an issue. 
+
+3) Fields that have a Java name with $ that matches a transpiled method name, 
+   such as toString$, will need to be refactored in Java to not have that name collision.
+   
+4) Fields in a subclass that have the same name as private fields in a superclass
+   represent a name collision, because the superclass method needs to call its private
+   field even if invoked from a subclass. The solution was to modify the subclass field
+   name using one or more prepended $.
+   
+5) Use of Class.getDeclaredMethods() reflection will return Method objects having the transpiled 
+   name, not the Java name. This could require some j2sNative adjustment 
+   to strip the $... parameters from the name if that is needed. 
+
+6) Use of Method.getParameterTypes() should work fine, provided class names
+   do not contain "_". This is because the transpiler converts "." to "_" when
+   creating the fully qualified JavaScript name.
+
+
+positive integers do not add to give negative numbers
+-----------------------------------------------------
+
+In Java, the following is true:
+
+  2000000000 + 2000000000 == -294967296
+
+But in SwingJS, that will be 4000000000. So, for example, the following
+strategy will fail in SwingJS:
+
+               int newLength = lineBuf.length * 2;
+               if (newLength < 0) {
+                       newLength = Integer.MAX_VALUE;
+               }
+
+"-1" in JavaScript is not 0xFFFFFFFF.
+
+And one must take care to not compare a negative number with a 32-bit mask. So
+
+(b & 0xFF000000) == 0xFF000000
+
+is true in Java for (int) b = -1, but is false in JavaScript, because 0xFF000000 is 4278190080, 
+while (-1 & 0xFF000000) is, strangely enough, -16777216, and, in fact, 
+
+(0xFF000000 & 0xFF000000) != 0xFF000000
+
+because -16777216 is not 4278190080.
+
+The fix is that one must compare similar operations:
+
+if ((b & 0xFF000000) == (0xFF000000 & 0xFF000000)) .....
+
+Importantly, the JavaScript Int32Array does behave properly. From 
+the Firefox developer console:
+
+>> x = new Int32Array(1)
+<- Int32Array(1) [ 0 ]
+>> x[0] = 4000000000
+<- 4000000000
+>> x[0]
+<- -294967296
+
+Notice that, perhaps unexpectedly, the following two constructs produce 
+different results in JavaScript:
+
+x = new Int32Array(1);
+b = x[0] = 4000000000;
+
+(b will be 4000000000)
+
+and
+
+x = new Int32Array(1);
+x[0] = 4000000000;
+b = x[0];
+
+(b will be -294967296)
+
+
+SwingJS leverages array typing to handle all byte and short arithmetic so as
+to ensure that any byte or short operation in JavaScript does give the same 
+result in Java. The design decision to not also do this with integer math was
+a trade-off between performance and handling edge cases.
+
+
+ArrayIndexOutOfBounds
+---------------------
+
+You cannot implicitly throw an ArrayIndexOutOfBoundsException in JavaScript.
+JavaScript will simply return "undefined", not throw an Exception. So:
+
+boolean notAGoodIdeaIsOutOfBounds(String[] sa, int i) {
+  try {
+     return (sa[i] == sa[i]);
+  } catch (ArrayIndexOutOfBoundsException e) {
+       return false;
+  }
+}
+
+will work in Java but not in JavaScript. Code should not depend upon this sort 
+of trap anyway, if you ask me. 
+
+Throwable vs Error vs Exception
+-------------------------------
+
+True JavaScript errors are trapped as Throwable, whereas you can still trap
+Error and Exception as well. So if you want to be sure to catch any JavaScript
+error, use try{}catch (Throwable t){}, not try{}catch (Exception e){}. 
+
+j
+ava.awt.Color
+--------------
+
+ColorSpace: only "support" CS_sRGB.
+
+ TODO -- any volunteers??
+
+javax.swing.JFileDialog
+-----------------------
+
+HTML5 cannot expose a file reading directory structure. But you certainly 
+can still do file reading and writing. It just works a little differently.
+It's a simple modification:
+
+               b = new JButton("FileOpenDialog");
+               b.addActionListener(new ActionListener() {
+
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               JFileChooser fc = new JFileChooser();
+                               Test_Dialog.this.onDialogReturn(fc.showOpenDialog(Test_Dialog.this));
+                               // Java will wait until the dialog is closed, then enter the onDialogReturn method.
+                               // JavaScript will exit with NaN immediately, and then call back with its actual value
+                               // asynchronously.
+                       }
+
+               });
+       
+               public void onDialogReturn(int value) {
+                       if (value != Math.floor(value))
+                               return; // in JavaScript, this will be NaN, indicating the dialog has been opened
+                       // If we are here, the dialog has closed, in both Java and JavaScript.
+                       System.out.println("int value is " + value);
+               }
+
+
+       @Override
+       public void propertyChange(PropertyChangeEvent event) {
+               Object val = event.getNewValue();
+               String name = event.getPropertyName();
+               System.out.println(name);
+               switch (event.getSource().getClass().getName()) {
+               case "javax.swing.JOptionPane":
+                       switch (name) {
+                       case "inputValue":
+                               onDialogReturn(val);
+                               return;
+                       case "value":
+                               if (val instanceof Integer)
+                                       onDialogReturn(((Integer) val).intValue());
+                               else
+                                       onDialogReturn(val);
+                               return;
+                       }
+                       break;
+               case "javax.swing.ColorChooserDialog":
+                       switch (name) {
+                       case "SelectedColor":
+                               onDialogReturn(val);
+                               return;
+                       }
+                       break;
+               case "javax.swing.JFileChooser":
+                       switch (name) {
+                       case "SelectedFile":
+                               File file = (File) val;
+                               byte[] array = (val == null ? null : /** @j2sNative file.秘bytes || */
+                                               null);
+                               onDialogReturn("fileName is '" + file.getName() + "'\n\n" + new String(array));
+                               return;
+                       }
+                       break;
+               }
+               System.out.println(
+                               event.getSource().getClass().getName() + " " + event.getPropertyName() + ": " + event.getNewValue());
+       }
+
+
+Developers are encouraged to create a separate class that handles general calls to JFileDialog. 
+An example class can be found in the SwingJS distribution as 
+
+/sources/net.sf.j2s.java.core/src/javajs/async/AsyncFileChooser.java.
+
+
+javax.swing.JOptionPane dialogs
+-------------------------------
+
+For this action to work, the parentComponent must implement
+propertyChangeListener, and any call to JOptionPanel should allow for
+an asynchronous response, meaning that there is no actionable code following the
+call to the dialog opening. 
+
+In addition, for compatibility with the Java version, implementation should
+wrap the call to getConfirmDialog or getOptionDialog in a method call to
+handle the Java:
+
+onDialogReturn(JOptionPane.showConfirmDialog(parentFrame,
+messageOrMessagePanel, "title", JOptionPane.OK_CANCEL_OPTION));
+
+Then parentFrame.propertyChange(event) should also call onDialogReturn.
+
+This will then work in both Java and JavaScript.
+
+Note that there is an int and an Object version of onDialogReturn().
+
+
+In JavaScript:
+
+The initial return from JOptionPane.showConfirmDialog and showMessageDialog
+will be (SwingJS) JDialog.ASYNCHRONOUS_INTEGER (NaN), testable as an impossible 
+Java int value using ret != -(-ret) if the parent implements PropertyChangeListener, or -1
+(CLOSE_OPTION) if not.
+
+For showOptionDialog (which returns Object) or showInputDialog (which returns
+String), the initial return will be (SwingJS) JDialog.ASYNCHRONOUS_OBJECT, testable as
+((Object) ret) instanceof javax.swing.plaf.UIResource if the parent implements
+PropertyChangeListeneer, or null if not.
+
+The second return will be the desired return.
+
+In Java:
+
+The initial return will be the one and only modal final return.
+
+
+
+For full compatibility, The calling method must not continue beyond this
+call.
+
+All of the standard Java events associated with Components are also
+available.
+
+Certain fall back mechanisms are possible, where onReturn does not exist, but
+only for the following cases:
+
+
+For showMessageDialog, for WARNING_MESSAGE and ERROR_MESSAGE, a simple
+JavaScript alert() is used, returning 0 (OK_OPTION) or -1 (CLOSED_OPTION).
+
+For showInputDialog, if the message is a string, a simple JavaScript prompt()
+with input box is used, returning the entered string or null.
+
+For showConfirmDialog, a simple JavaScript confirm() is used, in which case:
+
+for YES_NO_OPTION: YES_OPTION or NO_OPTION
+
+for YES_NO_CANCEL_OPTION: YES_OPTION or CANCEL_OPTION
+
+for OK_CANCEL_OPTION or any other: OK_OPTION or CANCEL_OPTION
+
+Note that you should implement a response for CLOSED_OPTION for
+showConfirmDialog. For other dialogs, a null return indicates the dialog was
+closed, just as for Java.
+
+Developers are encouraged to create a separate class that handles general calls. 
+An example class can be found in the SwingJS distribution as src/javajs/async/AsyncDialog.java.
+Very simple modifications to the Java allows asynchronous operation using AsyncDialog. Here
+is a simple "do you want to close this frame" example, where you can see that what we have
+done is to set the reply into an ActionListener that is defined in the constructor of 
+the AsyncDisplay object:
+
+// Original:
+//
+//     private void promptQuit() {
+//             int sel = JOptionPane.showConfirmDialog(null, PROMPT_EXIT, NAME, JOptionPane.YES_NO_OPTION);
+//             switch (sel) {
+//             case JOptionPane.YES_OPTION:
+//                     resultsTab.clean();
+//                     seqs.dispose();
+//                     if (fromMain) {
+//                             System.exit(0);
+//                     }
+//                     break;
+//             }
+//     }
+
+       private void promptQuitAsync() {
+               new AsyncDialog(new ActionListener() {
+
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                           int sel = ((AsyncDialog)e.getSource()).getOption();
+                               switch (sel) {
+                               case JOptionPane.YES_OPTION:
+                                       resultsTab.clean();
+                                       seqs.dispose();
+                                       if (fromMain) {
+                                               System.exit(0);
+                                       }
+                                       break;
+                               }
+                       }}).showConfirmDialog(null, PROMPT_EXIT, NAME, JOptionPane.YES_NO_OPTION);
+       }
+
+Very simple! 
+
+
+native methods
+--------------
+
+The J2S compiler ignores all static native method declarations.
+Anything of this nature needs to be implemented in JavaScript if it is needed,
+using j2sNative blocks:
+
+/**
+ * @j2sNative
+ *
+ *    var putYourJavaScriptCodeHere
+ *
+ */
+ Note that if you follow that directly with a {...} block, then 
+ the javadoc code will run in JavaScript, and the {...} code will run in Java.
+key Focus
+---------
+
+As of June, 2019, the keyboard focus manager is fully implemented. 
+The one catch is that JTextPane and JTextArea, which already consume
+VK_TAB in Java, cannot use CTRL-TAB to continue a tabbing cycle around
+the components in a window. Instead, CTRL-TAB is absorbed by the browser. 
+
+
+LookAndFeel and UI Classes
+--------------------------
+
+SwingJS implements the native browser look and feel as swingjs.plaf.HTML5LookAndFeel. 
+There are small differences between all look and feels -- MacOS, Windows, SwingJS.
+
+Expert developers know how to coerce changes in the UI by subclassing the UI for a 
+component. This probably will not work in SwingJS. 
+
+Note that LookAndFeel in Java usually determines canvas size in a Frame because 
+different operating systems (Mac OS vs Windows vs HTML5) will have 
+different edge sizes on their frames. If you want to ensure a component size, 
+use getContentPane().setPreferredSize().
+
+
+System.exit(0) does not stop all processes
+------------------------------------------
+
+Although System.ext(int) has been implemented in JavaScript, it just closes the 
+frames, stops all pending javax.swing.Timer objects in the queue, and runs any 
+threads added using Runtime.getRuntime().addShutdownHook(Thread).
+It may not stop all "threads." So don't rely on that.
+Applications are responsible for shutting down prior to executing System.exit(0). 
+
+
+myClass.getField not implemented
+--------------------------------
+
+java.lang.reflect.Field is implemented minimally. It is not
+certain that Field.getDeclaringClass() will work. If you just want a 
+value of a field, you can do this:
+
+/**
+ *@j2sNative
+ *
+ * return myClass[name]
+ */   
+
+But that is not a java.lang.reflection.Field object.
+
+
+"window" and other reserved JavaScript names
+--------------------------------------------
+
+No reserved top-level JavaScript name is allowed for a package name. So, for example, 
+one must rename packages such as "window" or "document" to names such as "win" or "doc".
+
+reserved field and method names
+-------------------------------
+
+In order to minimize the chance of added SwingJS field and method names colliding with ones 
+developers might use in subclassing Java classes, we have added U+79D8 (first character of Mandarin 
+"secret") to the characters already disrecommended by Java documentation ("$" and "_"). The only problem
+would be if you use that character followed by certain English words in certain classes. For example
+\u79D8canvas for JComponents (in java.awt.JSComponent) and \u79D8byte (in java.io.File).
+
+qualified field and method names
+--------------------------------
+
+Method names in SwingJS are fully qualified, meaning two methods with the same Java name but different
+parameters, such as write(int) and write(double), must not have the same name in JavaScript. (In this
+case, we will have write$I and write$D.) However, in certain cases it may be desirable to leave the
+method names unqualified. In particular, when an interface actually represents a JavaScript object, 
+the transpiler can leave a method name unqualified. The default situation for this is a class name 
+includes ".api.js" (case-sensitive). This means that any method in any class in a package js within 
+a package api, or any private interface js that has an outer interface api, will have all-unqualified
+methods. An example of this is swingjs.plaf.JSComboPopupList, which needs to communicate with a jQuery 
+object directly using the following interface:
+
+       private interface api {
+
+               interface js extends JQueryObject {
+
+                       abstract js j2sCB(Object options);
+
+                       abstract Object[] j2sCB(String method);
+
+                       abstract Object[] j2sCB(String method, Object o);
+
+                       abstract Object[] j2sCB(String method, int i);
+
+                       abstract int j2sCB(String OPTION, String name);
+
+               }
+       }
+
+Notice that all these variants of j2sCB() will call the same method in JavaScript by design.
+
+
+missing Math methods
+--------------------
+
+java.lang.Math is worked out, but some methods are missing, either because they
+involve long integer value that are inaccessible in JavaScript, or because I just
+didn't implement them. This is a result of continued Java development. 
+It is easy enough to add these methods if you have the source. They go into j2sClazz.js, 
+which is combined with other initial libraries into swingjs2.js by build_site.xml
+
+
+Component.getGraphics(), Graphics.dispose()
+-------------------------------------------
+
+Use of component.getGraphics() is discouraged in Java and in SwingJS. 
+Specifically in SwingJS, any call to component.getGraphics() or 
+BufferedImage.createGraphics() or Graphics.create(...) should be matched with graphics.dispose(), 
+particularly when it is called outside the context of a paint(Graphics)
+call from the system. 
+
+If you see your graphics scrolling down the page with each repaint, 
+look for where you have used Component.getGraphics() and not Graphics.dispose().
+For example, this will definitely NOT work in SwingJS:
+
+  this.paint(getGraphics())
+  
+and really should not work in Java, either, as it is technically a resource memory leak.
+
+Instead, if you really do not want to use repaint(), use this:
+
+  Graphics g = getGraphics();
+  paint(g);
+  g.dispose();
+
+
+
+Graphics.setClip()
+------------------
+
+The HTML5 canvas.clip() method is permanent. You can only reset the clip using
+save/restore. This is different from Java, where you can temporarily change it using
+
+  Shape oldClip = Graphics.getClip();
+  Graphics.setClip(newClip);
+   ...
+  Graphics.setClip(oldClip); 
+
+If you need to do something like this, you must schedule the paints
+to not have overlapping clip needs.
+
+
+MAJOR ISSUES--for Bob and Udo within SwingJS
+============================================
+
+fonts
+-----
+
+Fonts and FontMetrics will all be handled in JavaScript. Font matching will 
+not be exact, and composite (drawn) fonts will not be supported. 
+
+SwingJS handles calls such as font.getFontMetrics(g).stringWidth("xxx") by 
+creating a <div> containing that text, placing it in an obscure location on 
+the page, and reading div.getBoundingClientRect(). This is a VERY precise
+value, but can be a pixel or two off from what Java reports for the same font.
+OS-dependent classes
+--------------------
+
+Static classes such as:
+
+   java.awt.Toolkit
+   java.awt.GraphicsEnvironment
+   
+   
+which are created using Class.forName are implemented using classes in the swingjs package.
+
+AWTAccessor is not implemented. 
+
+   
+AWT component peers and component "ui" user interfaces
+------------------------------------------------------
+
+ComponentPeer is a class that represents a native AWT component.
+Components with such peers are called "heavy-weight" components.
+They are expected to do the dirty work of graphics drawing. 
+
+Java Swing implements peers only for JApplet, JDialog, JFrame, and JWindow. 
+References to such objects have been removed, but clearly there must be 
+some connection to similar DOM objects, even for "light-weight" components. 
+
+
+  
+MAJOR ISSUES--to be resolved by implementers
+============================================
+
+fonts
+-----
+
+Glyph/composite/outline fonts are not supported.
+   
+
+
+threads
+-------
+
+Thread locking and synchronization are not relevant to JavaScript.
+Thus, anything requiring "notify.." or "waitFor.." could be a serious issue.
+All threading must be "faked" in JavaScript. Specifically not available is:
+
+  Thread.sleep()
+  
+javax.swing.AbstractButton#doClick(pressTime) will not work, as it requires Thread.sleep();
+    
+However, java.lang.Thread itself is implemented and used extensively. 
+
+Methods thread.start() and thread.run() both work fine. 
+
+For simple applications that use Thread.sleep() just to have a delay, as in a frame rate, for 
+example, one can use javax.swing.Timer instead. That is fully implemented. 
+
+Likewise, java.util.Timer can be replaced with no loss of performance with javax.Swing.Timer.
+Note that java.util.TimerTask is implemented, but it can also be replaced by an implementation of Runnable.
+
+task = new TimerTask(){....};
+t = new java.util.Timer();
+t.schedule(task, 0, 1);
+
+becomes
+
+task = new TimerTask(){....}; // or task = new Runnable() {...}
+t = new javax.swing.Timer(1, new ActionListener() {
+       @Override
+       public void actionPerformed(ActionEvent e) {
+               task.run();
+       }
+};
+t.setInitialDelay(0); // not particularly necessary
+t.start();
+
+In addition, SwingJS provides swingjs.JSThread, which can be subclassed
+if desired. This class allows simple 
+
+  while(!interrupted()){
+       wait()
+       ...
+  }  
+
+action through an asynchronous function run1(mode). For example:
+
+       protected void run1(int mode) {
+               try {
+                       while (true)
+                               switch (mode) {
+                               case INIT:
+                                       // once-through stuff here
+                                       mode = LOOP;
+                                       break;
+                               case LOOP:
+                                       if (!doDispatch || isInterrupted()) {
+                                               mode = DONE;
+                                       } else {
+                                               Runnable r = new Runnable() {
+                                                       public void run() {
+                                                               // put the loop code here
+                                                       }
+                                               };
+                                               dispatchAndReturn(r);
+                                               if (isJS)
+                                                       return;
+                                       }
+                                       break;
+                               // add more cases as needed
+                               case DONE:
+                                       // finish up here
+                                       if (isInterrupted())
+                                               return;
+                                       // or here
+                                       break;
+                               }
+               } finally {
+                       // stuff here to be executed after each loop in JS or at the end in Java
+               }
+       }
+
+image loading
+-------------
+- All image loading in SwingJS is synchronous. A MediaTracker call will immediately return "complete".
+  However, it still may take one system clock tick to fully load images. Thus, it is recommended that
+  images be preloaded in the static block of the applet if it is necessary that they be available in init().
+  This is only an issue if you are trying to access the pixel buffer of the image in JavaScript. 
+  
+- Applet.getImage(path, name) will return null if the image does not exist. 
+
+- BufferedImage: only "support" imageType RGB and ARGB
+
+  -BH: This is a temporary edit, just to get us started. Certainly GRAY will be needed
+
+
+BigInteger and BigDecimal
+-------------------------
+
+java.math.BigInteger is fully supported; java.math.BigDecimal is roughed 
+in and not fully tested (07/2019). 
+
+Both classes present significant issues for JavaScript, as they are based in 
+Java's 64-bit long for all their operations. Here is the JavaDoc note I added
+to BigInteger:
+
+ * SwingJS note: Because of the limitations of JavaScript with regard
+ * to long-integer bit storage as a double, this implementation drops
+ * the integer storage bit length to 24, giving 48 for long and leaving
+ * the last 16 bits clear for the exponent of the double number. This should
+ * not affect performance significantly. It does increase the storage 
+ * size by about 33%. By bringing an "int" to 3 bytes, we can easily construct
+ * and use byte[] data intended for the original BitSet.  
+
+"Easily" may be a bit strong there. This was a serious challenge.
+
+BigDecimal seems to run normally, but in order to do that, my hack involves
+reducing the size of an integer that is allowed to be stored as such and not
+in byte[] as a BigInteger. I'm sure there is a performance hit, but it does work.
+
+no format internationalization
+------------------------------
+
+For now, just en for number and date formatters
+
+no winding rules
+----------------
+
+  When filling a graphic, only nonzero winding rule is implemented in HTML5 Canvas2D.
+
+
+
+text-related field implementation
+---------------------------------
+
+Text fields are:
+
+JTextField   (JavaScript <input type="text">)
+JTextArea    (JavaScript <textarea>)
+JTextPane    (JavaScript <div>)
+JEditorPane  (JavaScript <div>)
+
+For the initial implementation, we don't implement infinite undo/redo, and the abstract 
+document model is much less elaborate. Only PlainDocument (in the form of JSPlainDocument)
+is implemented. The Document returned by JTextField.getDocument() is a javax.swing.text.Document.
+
+All scrolling is handled by HTML5. javax.swing.AutoScroller is not implemented.
+public static methods .stop, .isRunning, .processMouseDragged require true Java threading
+and so are not implmented. javax.swing.text.View and its subclasses are not implemented. 
+
+The JS document model does not allow two text fields to address the same underlying document. 
+
+
+Formatter/Regex limitations
+---------------------------
+
+Some browsers cannot process Regex "look-behind" process such as (?<=\W)
+java.util.regex.Matcher and Pattern use JavaScript's RegExp object rather than
+the native Java object. These are not identical. Only flags /igm are supported.
+Matcher.start(groupID) is not supported.
+
+java.util.Formatter will function correctly for all standard %... patterns.
+
+integer 1/0 == Infinity
+-----------------------
+
+1/0 in Java throws "java.lang.ArithmeticException: / by zero", but in JavaScript is just Infinity. 
+
+
+Summary
+-------
+
+These are all the known limitations of SwingJS. We have not found any of these limitations
+to be show-stoppers. The primary issue for newcomers to SwingJS is having the source code.
+You must check that source code for all your library jar files is available or, if you
+choose, you will need to decompile those classes. We have used decompilation on some projects,
+and it works just fine. So, technically, all we really need are JAR/class files. But the 
+source is by far superior. It's generally prettier, and it has the license information that
+may or may not be present with the JAR or class files. Use class files at your own risk.
+
+Bob Hanson
+2019.08.16
+
+
+Additional Issues
+-----------------
+
+Annotation is working for classes, methods, and fields (12/22/19). Method reflection, however,
+is limited. Interfaces do not expose their methods, as the transpiler does not actually transpile
+the interfaces themselves. And method reflection only includes annotated methods.
+
+java.util.concurrent is not fully elaborated. This package is rewritten to not actually use the
+memory handling capabilities of concurrency, which JavaScript does not have access to.
+
+System.getProperties() just returns a minimal set of properties.
+
+
index 6c92ed6..49cba07 100644 (file)
@@ -1 +1 @@
-20200601093857 
+20200610103554 
diff --git a/template.html b/template.html
new file mode 100644 (file)
index 0000000..d132460
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>SwingJS test _NAME_</title><meta charset="utf-8" />
+<script src="swingjs/swingjs2.js"></script>
+<script>
+if (!self.SwingJS)alert('swingjs2.js was not found. It needs to be in swingjs folder in the same directory as ' + document.location.href)
+Info = {
+  code: _CODE_,
+  main: _MAIN_,
+  core: "NONE",
+       width: 850,
+       height: 550,
+  readyFunction: null,
+       serverURL: 'https://chemapps.stolaf.edu/jmol/jsmol/php/jsmol.php',
+       j2sPath: 'swingjs/j2s',
+       console:'sysoutdiv',
+       allowjavascript: true
+}
+</script>
+</head>
+<body>
+<script>
+SwingJS.getApplet('testApplet', Info)
+getClassList = function(){J2S._saveFile('_j2sclasslist.txt', Clazz.ClassFilesLoaded.sort().join('\n'))}
+</script>
+<div style="position:absolute;left:900px;top:30px;width:600px;height:300px;">
+<div id="sysoutdiv" contentEditable="true" style="border:1px solid green;width:100%;height:95%;overflow:auto"></div>
+This is System.out. <a href="javascript:testApplet._clearConsole()">clear it</a>  <a href='javascript:J2S.getProfile()'>start/stop profiling</a><br>see <a href=___j2sflags.htm>___j2sflags.htm</a> for SwingJS URL command-line options<br><a href="javascript:getClassList()">get _j2sClassList.txt</a>
+</div>
+</body>
+</html>
index 3b9be23..a566be9 100644 (file)
@@ -31,7 +31,7 @@ public class AlignmentSorterTest
     /*
      * sort with no score features does nothing
      */
-    PA.setValue(AlignmentSorter.class, "sortByFeatureCriteria", null);
+    PA.setValue(AlignmentSorter.getInstance(), "sortByFeatureCriteria", null);
 
     AlignmentSorter.sortByFeature(null, null, 0, al.getWidth(), al,
             AlignmentSorter.FEATURE_SCORE);
@@ -64,7 +64,7 @@ public class AlignmentSorterTest
      * sort by ascending score, no filter on feature type or group
      * NB sort order for the same feature set (none) gets toggled, so descending
      */
-    PA.setValue(AlignmentSorter.class, "sortByFeatureAscending", true);
+    PA.setValue(AlignmentSorter.getInstance(), "sortByFeatureAscending", true);
     AlignmentSorter.sortByFeature(null, null, 0, al.getWidth(), al,
             AlignmentSorter.FEATURE_SCORE);
     assertSame(al.getSequenceAt(3), seq3); // -0.5
index 2ca112f..0123551 100644 (file)
@@ -443,7 +443,7 @@ public class CrossRefTest
         return new SequenceI[] { pep1, pep2 };
       }
     };
-    SequenceFetcherFactory.setSequenceFetcher(mockFetcher);
+    SequenceFetcher.setMockFetcher(mockFetcher);
 
     /*
      * find UNIPROT xrefs for nucleotide sequence
@@ -459,7 +459,7 @@ public class CrossRefTest
   @AfterClass(alwaysRun = true)
   public void tearDown()
   {
-    SequenceFetcherFactory.setSequenceFetcher(null);
+    SequenceFetcher.setMockFetcher(null);
   }
 
   /**
@@ -521,7 +521,7 @@ public class CrossRefTest
         return new SequenceI[] { pep1, pep2 };
       }
     };
-    SequenceFetcherFactory.setSequenceFetcher(mockFetcher);
+    SequenceFetcher.setMockFetcher(mockFetcher);
 
     /*
      * find UNIPROT xrefs for gene and transcripts
@@ -681,7 +681,7 @@ public class CrossRefTest
         }
       }
     };
-    SequenceFetcherFactory.setSequenceFetcher(mockFetcher);
+    SequenceFetcher.setMockFetcher(mockFetcher);
 
     /*
      * find EMBL xrefs for Uniprot seqs and verify that
index 0846ec2..d9efa23 100644 (file)
@@ -44,7 +44,7 @@ public class FeatureAttributesTest
    * Test the method that keeps attribute names in non-case-sensitive order,
    * including handling of 'compound' names
    */
-  @Test(groups="Functional")
+  @Test(groups = "Functional")
   public void testAttributeNameComparator()
   {
     FeatureAttributes fa = FeatureAttributes.getInstance();
@@ -52,19 +52,24 @@ public class FeatureAttributesTest
             "comparator");
 
     assertEquals(
-            comp.compare(new String[] { "CSQ" }, new String[] { "csq" }), 0);
+            comp.compare(new String[]
+            { "CSQ" }, new String[] { "csq" }), 0);
 
-    assertTrue(comp.compare(new String[] { "CSQ", "a" },
-            new String[] { "csq" }) > 0);
+    assertTrue(
+            comp.compare(new String[]
+            { "CSQ", "a" }, new String[] { "csq" }) > 0);
 
-    assertTrue(comp.compare(new String[] { "CSQ" }, new String[] { "csq",
-        "b" }) < 0);
+    assertTrue(
+            comp.compare(new String[]
+            { "CSQ" }, new String[] { "csq", "b" }) < 0);
 
-    assertTrue(comp.compare(new String[] { "CSQ", "AF" }, new String[] {
-        "csq", "ac" }) > 0);
+    assertTrue(
+            comp.compare(new String[]
+            { "CSQ", "AF" }, new String[] { "csq", "ac" }) > 0);
 
-    assertTrue(comp.compare(new String[] { "CSQ", "ac" }, new String[] {
-        "csq", "AF" }) < 0);
+    assertTrue(
+            comp.compare(new String[]
+            { "CSQ", "ac" }, new String[] { "csq", "AF" }) < 0);
   }
 
   @Test(groups = "Functional")
@@ -124,10 +129,13 @@ public class FeatureAttributesTest
             "group");
     sf.setValue("kd", "-1");
     sf.setValue("domain", "Metal");
+    sf.setValue("foo", " ");
     sf.setValue("phase", "1");
-    sf.setValue("phase", "reverse");
+    sf.setValue("phase", "1reverse");
     assertEquals(fa.getDatatype("Pfam", "kd"), Datatype.Number);
     assertEquals(fa.getDatatype("Pfam", "domain"), Datatype.Character);
     assertEquals(fa.getDatatype("Pfam", "phase"), Datatype.Mixed);
+    assertNull(fa.getDatatype("Pfam", "unobserved"));
+    assertNull(fa.getDatatype("Pfam", "foo"));// empty values are ignored
   }
 }
index 9e9d9a4..92fef42 100644 (file)
@@ -54,13 +54,13 @@ public class EnsemblCdnaTest
   @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);
   }
 
   /**
index e7574eb..4521d9b 100644 (file)
@@ -53,13 +53,13 @@ public class EnsemblCdsTest
   @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);
   }
 
   /**
index 8b1e840..da129ba 100644 (file)
@@ -56,13 +56,13 @@ public class EnsemblGeneTest
   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);
   }
 
   /**
index 11140f9..0a9cc64 100644 (file)
@@ -52,13 +52,13 @@ public class EnsemblGenomeTest
   @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);
   }
 
   /**
index e17b4a6..a4d6d92 100644 (file)
@@ -135,13 +135,13 @@ public class EnsemblSeqProxyTest
   @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);
   }
 
   @DataProvider(name = "ens_seqs")
index e42b54f..8752f93 100644 (file)
@@ -61,7 +61,7 @@ public class JmolCommandsTest
     SequenceRenderer sr = new SequenceRenderer(af.getViewport());
     SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
     String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-    StructureSelectionManager ssm = new StructureSelectionManager();
+    StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(null);
 
     // need some mappings!
 
@@ -90,7 +90,7 @@ public class JmolCommandsTest
     SequenceRenderer sr = new SequenceRenderer(af.getViewport());
     SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
     String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-    StructureSelectionManager ssm = new StructureSelectionManager();
+    StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(null);
   
     /*
      * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
index e451ed2..7ef0d16 100644 (file)
@@ -74,7 +74,7 @@ public class JmolViewerTest
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+    jalview.gui.Desktop.getInstance().closeAll_actionPerformed(null);
   }
 
   @Test(groups = { "Functional" })
index 06a09df..a18a8a3 100644 (file)
@@ -184,7 +184,7 @@ public class ChimeraCommandsTest
     SequenceRenderer sr = new SequenceRenderer(af.getViewport());
     SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
     String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-    StructureSelectionManager ssm = new StructureSelectionManager();
+    StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(null);
 
     /*
      * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
index 734f7eb..194d34a 100644 (file)
@@ -95,7 +95,7 @@ public class JalviewChimeraView
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    Desktop.instance.closeAll_actionPerformed(null);
+    Desktop.getInstance().closeAll_actionPerformed(null);
   }
 
   @AfterMethod(alwaysRun = true)
index 00af72d..e51527a 100644 (file)
@@ -75,7 +75,7 @@ public class AlignFrameTest
   @AfterMethod(alwaysRun = true)
   public void tearDown()
   {
-    Desktop.instance.closeAll_actionPerformed(null);
+    Desktop.getInstance().closeAll_actionPerformed(null);
   }
 
   /**
index 76021ce..60e10d1 100644 (file)
@@ -55,6 +55,7 @@ import jalview.schemes.PIDColourScheme;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.MapList;
 import jalview.viewmodel.ViewportRanges;
+import jalview.workers.AlignCalcManager;
 
 public class AlignViewportTest
 {
@@ -73,14 +74,16 @@ public class AlignViewportTest
   @BeforeClass(alwaysRun = true)
   public static void setUpBeforeClass() throws Exception
   {
-    Jalview.main(new String[] { "-nonews", "-props",
+    Jalview.main(new String[] {
+        //"-jabaws", "none", 
+        "-nonews", "-props",
         "test/jalview/testProps.jvprops" });
 
     /*
      * remove any sequence mappings left lying around by other tests
      */
     StructureSelectionManager ssm = StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance);
+            .getStructureSelectionManager(Desktop.getInstance());
     ssm.resetAll();
   }
 
@@ -128,7 +131,7 @@ public class AlignViewportTest
      * mappings
      */
     StructureSelectionManager ssm = StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance);
+            .getStructureSelectionManager(Desktop.getInstance());
     List<AlignedCodonFrame> sequenceMappings = ssm.getSequenceMappings();
     assertEquals(2, sequenceMappings.size());
     assertTrue(sequenceMappings.contains(acf1));
@@ -150,10 +153,10 @@ public class AlignViewportTest
   @Test(groups = { "Functional" })
   public void testDeregisterMapping_withNoReference()
   {
-    Desktop d = Desktop.instance;
+    Desktop d = Desktop.getInstance();
     assertNotNull(d);
     StructureSelectionManager ssm = StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance);
+            .getStructureSelectionManager(Desktop.getInstance());
     ssm.resetAll();
 
     AlignFrame af1 = new FileLoader().LoadFileWaitTillLoaded(
@@ -213,10 +216,10 @@ public class AlignViewportTest
   @Test(groups = { "Functional" })
   public void testDeregisterMapping_withReference()
   {
-    Desktop d = Desktop.instance;
+    Desktop d = Desktop.getInstance();
     assertNotNull(d);
     StructureSelectionManager ssm = StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance);
+            .getStructureSelectionManager(Desktop.getInstance());
     ssm.resetAll();
 
     AlignFrame af1 = new FileLoader().LoadFileWaitTillLoaded(
@@ -319,15 +322,19 @@ public class AlignViewportTest
   {
     synchronized (this)
     {
-      while (viewport.getCalcManager().isWorking())
+      System.out.print("waiting...");
+      int n = 3;
+      while (--n >= 0 || viewport.getCalcManager().isWorking())
       {
         try
         {
+          System.out.print(((AlignCalcManager) viewport.getCalcManager()).getQueueLength());
           wait(50);
         } catch (InterruptedException e)
         {
         }
       }
+           System.out.println("...done");
     }
   }
 
@@ -478,7 +485,10 @@ public class AlignViewportTest
     AlignViewport testme = af.getViewport();
     waitForCalculations(testme);
     SequenceI cons = testme.getConsensusSeq();
-    assertEquals("A-C", cons.getSequenceAsString());
+    String s = cons.getSequenceAsString();
+    System.out.println("s is " + s);
+    
+    assertEquals("A-C", s);
   }
 
   @Test(groups = { "Functional" })
index 24b658f..3d06bc8 100644 (file)
@@ -40,6 +40,7 @@ import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
 import jalview.io.FileLoader;
+import jalview.util.Platform;
 import jalview.viewmodel.ViewportRanges;
 
 public class AlignmentPanelTest
@@ -49,8 +50,9 @@ public class AlignmentPanelTest
   @BeforeMethod(alwaysRun = true)
   public void setUp() throws InvocationTargetException, InterruptedException
   {
-    Jalview.main(new String[] { "-nonews", "-props",
-        "test/jalview/testProps.jvprops" });
+    Jalview.main(new String[] { "-nonews", 
+        "-props", "test/jalview/testProps.jvprops",
+        "-jabaws", "none"});
 
     Cache.setPropertyNoSave("SHOW_IDENTITY",
             Boolean.TRUE.toString());
@@ -94,22 +96,29 @@ public class AlignmentPanelTest
     af.alignPanel.setScrollValues(-1, 5);
 
     // setting -ve x value does not change residue
+    // no update necessary now
     assertEquals(ranges.getEndRes(), oldres);
 
     af.alignPanel.setScrollValues(0, 5);
-
+    // no update necessary now
     // setting 0 as x value does not change residue
+    // no update necessary now
     assertEquals(ranges.getEndRes(), oldres);
 
     af.alignPanel.setScrollValues(5, 5);
     // setting x value to 5 extends endRes by 5 residues
+    System.out.println(ranges);
+    // no update necessary now
     assertEquals(ranges.getEndRes(), oldres + 5);
 
     // scroll to position after hidden columns sets endres to oldres (width) +
     // position
-    int scrollpos = 60;
+
+    int scrollpos = 53; // was 60, but this is too high to allow full scrolling
+                        // in Windows
     af.getViewport().hideColumns(30, 50);
     af.alignPanel.setScrollValues(scrollpos, 5);
+    // no update necessary now
     assertEquals(ranges.getEndRes(), oldres + scrollpos);
 
     // scroll to position within hidden columns, still sets endres to oldres +
@@ -120,6 +129,7 @@ public class AlignmentPanelTest
     af.getViewport().showAllHiddenColumns();
     af.getViewport().hideColumns(30, 50);
     af.alignPanel.setScrollValues(scrollpos, 5);
+    // no update necessary now
     assertEquals(ranges.getEndRes(), oldres + scrollpos);
 
     // scroll to position within <width> distance of the end of the alignment
@@ -127,8 +137,9 @@ public class AlignmentPanelTest
     scrollpos = 130;
     af.getViewport().showAllHiddenColumns();
     af.alignPanel.setScrollValues(scrollpos, 5);
-    assertEquals(ranges.getEndRes(), af.getViewport()
-            .getAlignment().getWidth() - 1);
+    // no update necessary now
+    assertEquals(ranges.getEndRes(),
+            af.getViewport().getAlignment().getWidth() - 1);
 
     // now hide some columns, and scroll to position within <width>
     // distance of the end of the alignment
@@ -136,9 +147,12 @@ public class AlignmentPanelTest
     // columns
     af.getViewport().hideColumns(30, 50);
     af.alignPanel.setScrollValues(scrollpos, 5);
-    assertEquals(ranges.getEndRes(), af.getViewport()
-            .getAlignment().getWidth() - 1 - 21); // 21 is the number of hidden
-                                                  // columns
+    // no update necessary now
+    assertEquals(ranges.getEndRes(),
+            af.getViewport().getAlignment().getWidth() - 1 - 21); // 21 is the
+                                                                  // number of
+                                                                  // hidden
+                                                                  // columns
   }
 
   /**
@@ -192,8 +206,9 @@ public class AlignmentPanelTest
      * note 4 pixels padding are added to the longest sequence name width
      */
     av.setIdWidth(-1); // force recalculation
+    
     d = af.alignPanel.calculateIdWidth();
-    assertEquals(d.width, 166); // 4 + pixel width of "Q93Z60_ARATH/1-118"
+    assertEquals(d.width, Platform.isWin() ? 172 : 166); // 4 + pixel width of "Q93Z60_ARATH/1-118"
     assertEquals(d.height, 12);
     assertEquals(d.width, av.getIdWidth());
   }
@@ -215,7 +230,7 @@ public class AlignmentPanelTest
      * note 4 pixels 'padding' are added to the longest seq name/annotation label
      */
     Dimension d = af.alignPanel.calculateIdWidth(2000);
-    assertEquals(d.width, 166); // 4 + pixel width of "Q93Z60_ARATH/1-118"
+    assertEquals(d.width, Platform.isWin() ? 172 : 166); // 4 + pixel width of "Q93Z60_ARATH/1-118"
     assertEquals(d.height, 12); // fixed value (not used?)
     assertEquals(av.getIdWidth(), 18); // not changed by this method
 
@@ -224,11 +239,14 @@ public class AlignmentPanelTest
      */
     SequenceI seq = af.viewport.getAlignment()
             .findSequenceMatch("Q93Z60_ARATH")[0];
-    seq.setName(seq.getName() + "MMMMM");
+    String orig = seq.getName();
+    seq.setName(orig + "MMMMM");
     d = af.alignPanel.calculateIdWidth(2000);
-    assertEquals(d.width, 211); // 4 + pixel width of "Q93Z60_ARATHMMMMM/1-118"
+    assertEquals(d.width, Platform.isWin() ? 219 : 211); // 4 + pixel width of "Q93Z60_ARATHMMMMM/1-118"
     assertEquals(d.height, 12);
     assertEquals(av.getIdWidth(), 18); // unchanged
+    // for next test:
+    seq.setName(orig);
 
     /*
      * make the longest annotation name even longer
@@ -239,17 +257,19 @@ public class AlignmentPanelTest
     FontMetrics fmfor = af.alignPanel
             .getFontMetrics(af.alignPanel.getAlabels().getFont());
     // Assumption ID_WIDTH_PADDING == 4
+    // AH! But with those added MMMM above, this was NOT the longest label!
     int expwidth = 4 + fmfor.stringWidth(aa.label);
     d = af.alignPanel.calculateIdWidth(2000);
-    assertEquals(d.width, expwidth); // 228 == ID_WIDTH_PADDING + pixel width of "THIS IS A VERY LONG LABEL INDEED"
+    assertEquals(d.width, expwidth); // 191 == ID_WIDTH_PADDING + pixel width of "THIS IS A VERY LONG LABEL INDEED"
     assertEquals(d.height, 12);
 
     /*
      * override with maxwidth
      * note the 4 pixels padding is added to this value
      */
-    d = af.alignPanel.calculateIdWidth(213);
-    assertEquals(d.width, 217);
+    // BH but we have to be under the max width
+    d = af.alignPanel.calculateIdWidth(180);
+    assertEquals(d.width, 184);
     assertEquals(d.height, 12);
   }
 
@@ -261,7 +281,7 @@ public class AlignmentPanelTest
      */
     int w = af.alignPanel.getVisibleIdWidth(true);
     assertEquals(w, af.alignPanel.getIdPanel().getWidth());
-    assertEquals(w, 115);
+    assertEquals(w, Platform.isWin() ? 112 : 115);
 
     /*
      * width for offscreen rendering is the same
@@ -281,6 +301,6 @@ public class AlignmentPanelTest
      * preference for auto id width overrides fixed width
      */
     Cache.setProperty("FIGURE_AUTOIDWIDTH", Boolean.TRUE.toString());
-    assertEquals(115, af.alignPanel.getVisibleIdWidth(false));
+    assertEquals(Platform.isWin() ? 106 : 115, af.alignPanel.getVisibleIdWidth(false));
   }
 }
index 43064a3..66e0d22 100644 (file)
@@ -77,7 +77,7 @@ public class FreeUpMemoryTest
     }
     doStuffInJalview(f);
 
-    Desktop.instance.closeAll_actionPerformed(null);
+    Desktop.getInstance().closeAll_actionPerformed(null);
 
     checkUsedMemory(expectedMin);
   }
@@ -120,7 +120,7 @@ public class FreeUpMemoryTest
      * sanity check - fails if any frame was added after
      * closeAll_actionPerformed
      */
-    assertEquals(Desktop.instance.getAllFrames().length, 0);
+    assertEquals(Desktop.getInstance().getAllFrames().length, 0);
 
     /*
      * if this assertion fails
index 61c5c9b..214a3ad 100644 (file)
@@ -86,7 +86,12 @@ public class JvSwingUtilsTest
             JvSwingUtils.wrapTooltip(true, tip));
 
     tip = "0123456789012345678901234567890123456789012345678901234567890"; // 61
-    assertFalse(tip.equals(JvSwingUtils.wrapTooltip(false, tip)));
+
+    // n/a -- message is too long for "false"
+//  
+//    assertFalse(tip.equals(JvSwingUtils.wrapTooltip(false, tip)));
+//    
+    
     assertFalse(("<html>" + tip + "</html>").equals(JvSwingUtils
             .wrapTooltip(true, tip)));
   }
@@ -100,9 +105,21 @@ public class JvSwingUtilsTest
   public void testWrapTooltip_multilineShortText()
   {
     String tip = "Now is the winter of our discontent<br>Made glorious summer by this sun of York";
-    assertEquals(tip, JvSwingUtils.wrapTooltip(false, tip));
-    assertEquals("<html>" + tip + "</html>",
-            JvSwingUtils.wrapTooltip(true, tip));
+    String tip2 = "Now is the winter of our discontent<br/>Made glorious summer by this sun of York";
+
+// BH not applicable in Jalview; "false" is only for when no <br> and only for short j2s2Discover messages
+//
+//    String s = JvSwingUtils.wrapTooltip(false, tip);
+//    System.out.println("<html>" + tip + "</html>");
+//    assertEquals(tip, s);
+    
+    String s;
+    s = JvSwingUtils.wrapTooltip(true, tip);
+    System.out.println(s);
+    assertEquals("<html>" + tip + "</html>", s);
+    s = JvSwingUtils.wrapTooltip(true, tip2);
+    System.out.println(s);
+    assertEquals("<html>" + tip + "</html>", s);
   }
 
   /**
@@ -112,11 +129,13 @@ public class JvSwingUtilsTest
   @Test(groups = { "Functional" })
   public void testWrapTooltip_longText()
   {
+    // BH should work in Java and JavaScript
     String tip = "Now is the winter of our discontent made glorious summer by this sun of York";
-    String expected = "<style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
-            + "<div class=\"ttip\">" + tip + " </div>";
-    assertEquals("<html>" + expected + "</html>",
-            JvSwingUtils.wrapTooltip(true, tip));
-    assertEquals(expected, JvSwingUtils.wrapTooltip(false, tip));
+    String expected = JvSwingUtils.HTML_PREFIX + tip + "</div></html>";
+    String s = JvSwingUtils.wrapTooltip(true, tip);
+    assertEquals(expected, s);
+ // BH not applicable in Jalview; "false" is only for when no <br> and only for short j2s2Discover messages
+//    s = JvSwingUtils.wrapTooltip(false, tip);
+//    assertEquals(expected, s);
   }
 }
index 3322ee8..ca9ac22 100644 (file)
@@ -37,7 +37,7 @@ public class PairwiseAlignmentPanelTest
 
     PairwiseAlignPanel testee = new PairwiseAlignPanel(viewport);
 
-    String text = ((JTextArea) PA.getValue(testee, "textarea")).getText();
+    String text = ((JTextArea) PA.getValue(testee, "textarea")).getText().replace("\r\n", "\n");
     String expected = "Score = 80.0\n" + "Length of alignment = 4\n"
             + "Sequence     FER1_PEA/29-32 (Sequence length = 7)\n"
             + "Sequence Q93XJ9_SOLTU/23-26 (Sequence length = 7)\n\n"
@@ -62,7 +62,7 @@ public class PairwiseAlignmentPanelTest
 
     PairwiseAlignPanel testee = new PairwiseAlignPanel(viewport);
 
-    String text = ((JTextArea) PA.getValue(testee, "textarea")).getText();
+    String text = ((JTextArea) PA.getValue(testee, "textarea")).getText().replace("\r\n", "\n");
     String expected = "Score = 80.0\n" + "Length of alignment = 4\n"
             + "Sequence     FER1_PEA/29-32 (Sequence length = 7)\n"
             + "Sequence Q93XJ9_SOLTU/23-26 (Sequence length = 7)\n\n"
index cc13fb9..4a5c248 100644 (file)
@@ -201,9 +201,10 @@ public class PopupMenuTest
     testee.configureReferenceAnnotationsMenu(menu, seqs);
     assertTrue(menu.isEnabled());
     String s = MessageManager.getString("label.add_annotations_for");
-    String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
-            + "<div class=\"ttip\">" + s
-            + "<br/>Jmol/secondary structure<br/>PDB/Temp </div></html>";
+//    String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
+//            + "<div class=\"ttip\">" + s
+//            + "<br/>Jmol/secondary structure<br/>PDB/Temp </div></html>";
+    String expected = "<html>" + s + "<br>Jmol/secondary structure<br>PDB/Temp</html>";
     assertEquals(expected, menu.getToolTipText());
   }
 
@@ -225,10 +226,13 @@ public class PopupMenuTest
     testee.configureReferenceAnnotationsMenu(menu, seqs);
     assertTrue(menu.isEnabled());
     String s = MessageManager.getString("label.add_annotations_for");
-    String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
-            + "<div class=\"ttip\">" + s
-            + "<br/>Jmol/secondary structure<br/>PDB/Temp </div></html>";
-    assertEquals(expected, menu.getToolTipText());
+//    String expected = "<html><style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style>"
+//            + "<div class=\"ttip\">" + s
+//            + "<br/>Jmol/secondary structure<br/>PDB/Temp</html>";
+    String expected = "<html>" + s
+            + "<br>Jmol/secondary structure<br>PDB/Temp</html>";
+    s = menu.getToolTipText();
+    assertEquals(expected, s);
   }
 
   /**
index e3b1191..21d67f4 100644 (file)
@@ -22,21 +22,31 @@ package jalview.gui;
 
 import static org.testng.Assert.assertEquals;
 
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.datamodel.AlignmentI;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+import jalview.util.Platform;
+import jalview.viewmodel.ViewportRanges;
+
 import java.awt.Font;
 import java.awt.FontMetrics;
 
-import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
-import jalview.bin.Cache;
-import jalview.datamodel.AlignmentI;
-import jalview.io.DataSourceType;
-import jalview.io.FileLoader;
 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
@@ -45,6 +55,9 @@ public class SeqCanvasTest
   @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);
@@ -57,8 +70,8 @@ public class SeqCanvasTest
     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
@@ -69,7 +82,8 @@ public class SeqCanvasTest
     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
@@ -191,7 +205,7 @@ public class SeqCanvasTest
     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);
@@ -213,6 +227,8 @@ public class SeqCanvasTest
   @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);
@@ -223,9 +239,10 @@ public class SeqCanvasTest
     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;
   
     /*
@@ -235,9 +252,12 @@ public class SeqCanvasTest
     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();
 
     /*
@@ -306,56 +326,47 @@ public class SeqCanvasTest
    * endSeq should be unchanged, but the vertical repeat height should include
    * all sequences.
    */
-  @Test(groups = "Functional_Failing")
+  @Test(groups = "Functional")
   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);
-    av.getRanges().setStartEndSeq(0, 3);
-    av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
-    av.setWrapAlignment(true);
+
+    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.setShowAnnotation(false);
     av.setScaleAboveWrapped(true);
 
     SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
-
+    av.setWrapAlignment(true);
+    av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
     int charHeight = av.getCharHeight();
     int charWidth = av.getCharWidth();
-    assertEquals(charHeight, 17);
-    assertEquals(charWidth, 12);
-
+    // Windows h=19, w=11.
+    assertEquals(charHeight, !Platform.isWin() ? 17 : 19);
+    assertEquals(charWidth, !Platform.isWin() ? 12 : 11);
+    
     int canvasWidth = 400;
     int canvasHeight = 300;
     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
-
-    assertEquals(av.getRanges().getEndSeq(), 3); // unchanged
+    System.out.println(ranges);
+    assertEquals(ranges.getEndSeq(), endSeq); // unchanged
     int repeatingHeight = (int) PA.getValue(testee,
             "wrappedRepeatHeightPx");
-    assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
-  }
-
-  @BeforeMethod(alwaysRun = true)
-  public void setUp()
-  {
-    Cache.loadProperties("test/jalview/io/testProps.jvprops");
-    Cache.setPropertyNoSave("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());
+    int h = charHeight * (2 + al.getHeight());
+    assertEquals(repeatingHeight, h);
   }
 }
index 2fdbe7e..01eec32 100644 (file)
@@ -252,7 +252,7 @@ public class SeqPanelTest
   @AfterMethod(alwaysRun = true)
   public void tearDown()
   {
-    Desktop.instance.closeAll_actionPerformed(null);
+    Desktop.getInstance().closeAll_actionPerformed(null);
   }
 
   @Test(groups = "Functional")
index 6ea3883..beeb52f 100644 (file)
@@ -207,7 +207,7 @@ public class AnnotatedPDBFileInputTest
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+    jalview.gui.Desktop.getInstance().closeAll_actionPerformed(null);
 
   }
 
index 3ca6ed8..59cb3cd 100644 (file)
@@ -184,7 +184,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
       {
         // retrieve dbref
 
-        SequenceFetcher sf = new SequenceFetcher(Desktop.instance,
+        SequenceFetcher sf = new SequenceFetcher(Desktop.getInstance(),
                 forSource, forAccession);
         sf.run();
         AlignFrame[] afs = Desktop.getAlignFrames();
@@ -211,7 +211,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
       }
       else
       {
-        Desktop.instance.closeAll_actionPerformed(null);
+        Desktop.getInstance().closeAll_actionPerformed(null);
         // recover stored project
         af = new FileLoader(false).LoadFileWaitTillLoaded(
                 savedProjects.get(first).toString(), DataSourceType.FILE);
@@ -278,7 +278,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
           }
           else
           {
-            Desktop.instance.closeAll_actionPerformed(null);
+            Desktop.getInstance().closeAll_actionPerformed(null);
             pass3 = 0;
             // recover stored project
             File storedProject = savedProjects.get(nextxref);
@@ -389,7 +389,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
                 }
                 else
                 {
-                  Desktop.instance.closeAll_actionPerformed(null);
+                  Desktop.getInstance().closeAll_actionPerformed(null);
                   // recover stored project
                   File storedProject = savedProjects.get(nextnextxref);
                   if (storedProject == null)
index b753e94..7b3373c 100644 (file)
@@ -72,7 +72,7 @@ public class FeaturesFileTest
      * remove any sequence mappings created so they don't pollute other tests
      */
     StructureSelectionManager ssm = StructureSelectionManager
-            .getStructureSelectionManager(Desktop.instance);
+            .getStructureSelectionManager(Desktop.getInstance());
     ssm.resetAll();
   }
 
index fbdd782..3a0560c 100644 (file)
@@ -70,16 +70,16 @@ public class Jalview2xmlBase
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+    jalview.gui.Desktop.getInstance().closeAll_actionPerformed(null);
   }
 
   @BeforeTest(alwaysRun = true)
   public static void clearDesktop()
   {
-    if (Desktop.instance != null && Desktop.getFrames() != null
+    if (Desktop.getInstance() != null && Desktop.getFrames() != null
             && Desktop.getFrames().length > 0)
     {
-      Desktop.instance.closeAll_actionPerformed(null);
+      Desktop.getInstance().closeAll_actionPerformed(null);
     }
   }
 
index 06d177d..1614453 100644 (file)
@@ -63,7 +63,7 @@ public class JalviewExportPropertiesTests
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
+    jalview.gui.Desktop.getInstance().closeAll_actionPerformed(null);
 
   }
 
index 772ed2b..f3a7586 100644 (file)
@@ -69,15 +69,15 @@ public class SequenceAnnotationReportTest
     sar.appendFeature(sb, 2, null, sf, null, 0);
     assertEquals("123456", sb.toString());
 
-    // residuePos == 1 matches start of feature, text appended (but no <br/>)
+    // residuePos == 1 matches start of feature, text appended (but no <br>)
     // feature score is not included
     sar.appendFeature(sb, 1, null, sf, null, 0);
     assertEquals("123456disulfide bond 1:3", sb.toString());
 
     // residuePos == 3 matches end of feature, text appended
-    // <br/> is prefixed once sb.length() > 6
+    // <br> is prefixed once sb.length() > 6
     sar.appendFeature(sb, 3, null, sf, null, 0);
-    assertEquals("123456disulfide bond 1:3<br/>disulfide bond 1:3",
+    assertEquals("123456disulfide bond 1:3<br>disulfide bond 1:3",
             sb.toString());
   }
 
@@ -147,8 +147,8 @@ public class SequenceAnnotationReportTest
      */
     minmax.put("METAL", new float[][] { { 0f, 1f }, null });
     sar.appendFeature(sb, 1, fr, sf, null, 0);
-    // <br/> is appended to a buffer > 6 in length
-    assertEquals("METAL 1 3; Fe2-S<br/>METAL 1 3; Fe2-S Score=1.3",
+    // <br> is appended to a buffer > 6 in length
+    assertEquals("METAL 1 3; Fe2-S<br>METAL 1 3; Fe2-S Score=1.3",
             sb.toString());
 
     /*
@@ -299,7 +299,7 @@ public class SequenceAnnotationReportTest
             null));
     sb.setLength(0);
     sar.createSequenceAnnotationReport(sb, seq, true, true, null);
-    String expected = "<i>SeqDesc<br/>Type1 ; Nonpos Score=1.0</i>";
+    String expected = "<i>SeqDesc<br>Type1 ; Nonpos Score=1.0</i>";
     assertEquals(expected, sb.toString());
 
     /*
@@ -324,7 +324,7 @@ public class SequenceAnnotationReportTest
 
     sb.setLength(0);
     sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
-    expected = "<i>SeqDesc<br/>Metal ; Desc<br/>Type1 ; Nonpos</i>";
+    expected = "<i>SeqDesc<br>Metal ; Desc<br>Type1 ; Nonpos</i>";
     assertEquals(expected, sb.toString());
     
     /*
@@ -349,7 +349,7 @@ public class SequenceAnnotationReportTest
     seq.addSequenceFeature(sf2);
     sb.setLength(0);
     sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
-    expected = "<i>SeqDesc<br/>Metal ; Desc<br/>Type1 ; Nonpos<br/>Variant ; Havana</i>";
+    expected = "<i>SeqDesc<br>Metal ; Desc<br>Type1 ; Nonpos<br>Variant ; Havana</i>";
     assertEquals(expected, sb.toString());
 
     /*
@@ -370,13 +370,13 @@ public class SequenceAnnotationReportTest
     fc.setAttributeName("clinical_significance");
     fr.setColour("Variant", fc);
     sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
-    expected = "<i>SeqDesc<br/>UNIPROT P30419<br/>PDB 3iu1<br/>Metal ; Desc<br/>"
-            + "Type1 ; Nonpos<br/>Variant ; Havana; clinical_significance=benign</i>";
+    expected = "<i>SeqDesc<br>UNIPROT P30419<br>PDB 3iu1<br>Metal ; Desc<br>"
+            + "Type1 ; Nonpos<br>Variant ; Havana; clinical_significance=benign</i>";
     assertEquals(expected, sb.toString());
     // with showNonPositionalFeatures = false
     sb.setLength(0);
     sar.createSequenceAnnotationReport(sb, seq, true, false, fr);
-    expected = "<i>SeqDesc<br/>UNIPROT P30419<br/>PDB 3iu1</i>";
+    expected = "<i>SeqDesc<br>UNIPROT P30419<br>PDB 3iu1</i>";
     assertEquals(expected, sb.toString());
 
     /*
@@ -386,7 +386,7 @@ public class SequenceAnnotationReportTest
     sf2.setDescription(
             "This is a very long description which should be truncated");
     sar.createSequenceAnnotationReport(sb, seq, false, true, fr);
-    expected = "<i>SeqDesc<br/>Metal ; Desc<br/>Type1 ; Nonpos<br/>Variant ; This is a very long description which sh...; clinical_significance=benign</i>";
+    expected = "<i>SeqDesc<br>Metal ; Desc<br>Type1 ; Nonpos<br>Variant ; This is a very long description which sh...; clinical_significance=benign</i>";
     assertEquals(expected, sb.toString());
 
     // see other tests for treatment of status and html
@@ -421,10 +421,10 @@ public class SequenceAnnotationReportTest
     String report = sb.toString();
     assertTrue(report
             .startsWith(
-                    "<i><br/>UNIPROT P30410, P30411, P30412, P30413,...<br/>PDB0 3iu1"));
+                    "<i><br>UNIPROT P30410, P30411, P30412, P30413,...<br>PDB0 3iu1"));
     assertTrue(report
             .endsWith(
-                    "<br/>PDB7 3iu1<br/>PDB8,...<br/>(Output Sequence Details to list all database references)</i>"));
+                    "<br>PDB7 3iu1<br>PDB8,...<br>(Output Sequence Details to list all database references)</i>"));
   }
 
   /**
index eb66416..95af77a 100644 (file)
@@ -39,6 +39,7 @@ 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;
 
@@ -94,6 +95,11 @@ import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 @Test(singleThreaded = true)
 public class Jalview2xmlTests extends Jalview2xmlBase
 {
+  @AfterMethod(alwaysRun = true)
+  public void tearDown()
+  {
+    Desktop.getInstance().closeAll_actionPerformed(null);
+  }
 
   @Override
   @BeforeClass(alwaysRun = true)
@@ -276,9 +282,9 @@ public class Jalview2xmlTests extends Jalview2xmlBase
     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.");
-
   }
 
   /**
@@ -410,7 +416,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
   @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);
@@ -438,7 +444,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
     {
       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);
@@ -464,7 +470,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
   @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.");
@@ -503,7 +509,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
     {
       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);
@@ -597,7 +603,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
   @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.");
@@ -672,7 +678,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
     {
       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);
@@ -717,7 +723,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
   @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);
@@ -766,7 +772,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
     {
       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);
@@ -817,7 +823,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
   @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);
 
@@ -880,7 +886,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
             ".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");
@@ -1089,7 +1095,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
   @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(
@@ -1133,7 +1139,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
   @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");
@@ -1167,10 +1173,10 @@ public class Jalview2xmlTests extends Jalview2xmlBase
     }
 
     // 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,
@@ -1200,7 +1206,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
   @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);
@@ -1236,7 +1242,7 @@ public class Jalview2xmlTests extends Jalview2xmlBase
     {
       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);
index 6b1fbd2..22294a6 100644 (file)
@@ -6,6 +6,13 @@ import static org.testng.Assert.assertNotEquals;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
+import java.awt.Color;
+import java.util.List;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
 import jalview.api.FeatureColourI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
@@ -17,13 +24,6 @@ import jalview.schemes.FeatureColour;
 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
 
-import java.awt.Color;
-import java.util.List;
-
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.BeforeTest;
-import org.testng.annotations.Test;
-
 /**
  * Unit tests for feature colour determination, including but not limited to
  * <ul>
@@ -53,7 +53,7 @@ public class FeatureColourFinderTest
 
   private FeatureRendererModel fr;
 
-  @BeforeTest(alwaysRun = true)
+  @BeforeClass(alwaysRun = true)
   public void setUp()
   {
     // aligned column 8 is sequence position 6
index 2e04ecb..679ba6b 100644 (file)
@@ -27,6 +27,16 @@ import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertSame;
 import static org.testng.Assert.assertTrue;
 
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
 import jalview.analysis.GeneticCodes;
 import jalview.api.AlignViewportI;
 import jalview.api.FeatureColourI;
@@ -46,17 +56,13 @@ import jalview.schemes.FeatureColour;
 import jalview.util.matcher.Condition;
 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
 
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.testng.annotations.Test;
-
 public class FeatureRendererTest
 {
+  @AfterMethod(alwaysRun = true)
+  public void tearDown()
+  {
+    Desktop.getInstance().closeAll_actionPerformed(null);
+  }
 
   @Test(groups = "Functional")
   public void testFindAllFeatures()
index 5db3743..06be31e 100644 (file)
@@ -172,7 +172,7 @@ public class ColourSchemesTest
   @AfterClass(alwaysRun = true)
   public static void tearDownAfterClass() throws Exception
   {
-    Desktop.instance.closeAll_actionPerformed(null);
+    Desktop.getInstance().closeAll_actionPerformed(null);
   }
 
   @Test(groups = "Functional")
index 4bee3f5..40715c8 100644 (file)
@@ -75,7 +75,8 @@ public class Mapping
       int coils[] = { 266, 275, 278, 287, 289, 298, 302, 316 }, helices[] = new int[]
       { 303, 315 }, sheets[] = new int[] { 267, 268, 269, 270 };
 
-      StructureSelectionManager ssm = new jalview.structure.StructureSelectionManager();
+      StructureSelectionManager ssm = StructureSelectionManager
+              .getStructureSelectionManager(null);
       StructureFile pmap = ssm.setMapping(true, new SequenceI[] { uprot },
               new String[] { "A" }, "test/jalview/ext/jmol/1QCF.pdb",
               DataSourceType.FILE);
@@ -144,7 +145,8 @@ public class Mapping
             "EIVKGVCSNFLCDLQPGDNVQITGPVGKEMLMPKDPNATIIMLATGTGIAPFRSFLWKMFFEKHDDYKFNGLGWLFLGVPTSSSLLYKEEFGKM");
     Sequence sq1 = new Sequence(sq);
     String inFile;
-    StructureSelectionManager ssm = new jalview.structure.StructureSelectionManager();
+    StructureSelectionManager ssm = StructureSelectionManager
+            .getStructureSelectionManager(null);
     // Associate the 1GAQ pdb file with the subsequence 'imported' from another
     // source
     StructureFile pde = ssm.setMapping(true, new SequenceI[] { sq },
@@ -243,7 +245,8 @@ public class Mapping
                     ">FER1_MAIZE/1-150 Ferredoxin-1, chloroplast precursor\nMATVLGSPRAPAFFFSSSSLRAAPAPTAVALPAAKVGIMGRSASSRRRLRAQATYNVKLITPEGEVELQVPD\nDVYILDQAEEDGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQSYLDDGQIADGWVLTCHAYPTSDVVIETHKE\nEELTGA",
                     DataSourceType.PASTE, FileFormat.Fasta);
     SequenceI newseq = seqf.getViewport().getAlignment().getSequenceAt(0);
-    StructureSelectionManager ssm = new jalview.structure.StructureSelectionManager();
+    StructureSelectionManager ssm = StructureSelectionManager
+            .getStructureSelectionManager(null);
     StructureFile pmap = ssm.setMapping(true, new SequenceI[] { newseq },
             new String[] { null }, "examples/3W5V.pdb",
             DataSourceType.FILE);
@@ -272,7 +275,8 @@ public class Mapping
     // make it harder by shifting the copy vs the reference
     newseq.setStart(refseq.getStart() + 25);
     newseq.setEnd(refseq.getLength() + 25 + refseq.getStart());
-    StructureSelectionManager ssm = new jalview.structure.StructureSelectionManager();
+    StructureSelectionManager ssm = StructureSelectionManager
+            .getStructureSelectionManager(null);
     ssm.setProcessSecondaryStructure(true);
     ssm.setAddTempFacAnnot(true);
     StructureFile pmap = ssm.setMapping(true, new SequenceI[] { newseq },
index e59648f..044cbc7 100644 (file)
@@ -80,7 +80,8 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
   public void setUp()
   {
     StructureImportSettings.setShowSeqFeatures(true);
-    ssm = new StructureSelectionManager();
+    ssm = StructureSelectionManager.getStructureSelectionManager(null);
+    ssm.resetAll();
   }
 
   @Test(groups = { "Functional" })
@@ -139,10 +140,11 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
     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));
   }
 
   /**
@@ -155,7 +157,7 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
     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 },
@@ -199,14 +201,14 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
   {
     // 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 },
index af02d5e..ac7e736 100644 (file)
@@ -130,7 +130,7 @@ public class AAStructureBindingModelTest
     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)
@@ -272,7 +272,7 @@ public class AAStructureBindingModelTest
     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);
index 9992137..b14b3b5 100644 (file)
@@ -149,7 +149,7 @@ public class PDBSequenceFetcherTest
         // chains in structure have a mapping to data in the structure
         List<SequenceFeature> prev = null;
         int lastp = -1;
-        for (int col = 1; col <= sq.getLength(); col++)
+        for (int col = 1, ns = sq.getLength(); col <= ns; col++)
         {
           List<SequenceFeature> sf = sq.findFeatures(col, col, "RESNUM");
           if (sf.size() != 1)
index 12f5e1b..9c291c3 100644 (file)
@@ -85,7 +85,7 @@ public class JalviewJabawsTestUtils
   public static Jws2Discoverer getJabawsDiscoverer(boolean localhost)
   {
     jalview.ws.jws2.Jws2Discoverer disc = jalview.ws.jws2.Jws2Discoverer
-            .getDiscoverer();
+            .getInstance();
     String svcurls = "";
     if (localhost)
     {
@@ -97,7 +97,7 @@ public class JalviewJabawsTestUtils
         services.add(url);
       }
       ;
-      Jws2Discoverer.getDiscoverer().setServiceUrls(services);
+      Jws2Discoverer.getInstance().setServiceUrls(services);
     }
     try
     {
index 6122d6d..4540bd8 100644 (file)
@@ -32,6 +32,7 @@ import jalview.datamodel.SequenceI;
 import jalview.gui.JvOptionPane;
 import jalview.io.DataSourceType;
 import jalview.structure.StructureMapping;
+import jalview.util.Platform;
 import jalview.xml.binding.sifts.Entry.Entity;
 
 import java.io.File;
@@ -189,7 +190,6 @@ public class SiftsClientTest
     Cache.loadProperties("test/jalview/io/testProps.jvprops");
     // SIFTs entries are updated weekly - so use saved SIFTs file to enforce
     // test reproducibility
-    new SiftsSettings();
     SiftsSettings.setSiftDownloadDirectory(jalview.bin.Cache.getDefault(
             "sifts_download_dir", DEFAULT_SIFTS_DOWNLOAD_DIR));
     SiftsSettings.setMapWithSifts(true);
@@ -519,17 +519,17 @@ groups = { "Network" },
   public void getLeadingIntegerFromString()
   {
     Assert.assertEquals(
-            SiftsClient.getLeadingIntegerValue("1234abcd", -1), 1234);
+            Platform.getLeadingIntegerValue("1234abcd", -1), 1234);
     Assert.assertEquals(
-            SiftsClient.getLeadingIntegerValue("1234", -1),
+            Platform.getLeadingIntegerValue("1234", -1),
             1234);
     Assert.assertEquals(
-            SiftsClient.getLeadingIntegerValue("abcd", -1), -1);
+            Platform.getLeadingIntegerValue("abcd", -1), -1);
     Assert.assertEquals(
-            SiftsClient.getLeadingIntegerValue("abcd1234", -1), -1);
+            Platform.getLeadingIntegerValue("abcd1234", -1), -1);
     Assert.assertEquals(
-            SiftsClient.getLeadingIntegerValue("None", -1), -1);
+            Platform.getLeadingIntegerValue("None", -1), -1);
     Assert.assertEquals(
-            SiftsClient.getLeadingIntegerValue("Null", -1), -1);
+            Platform.getLeadingIntegerValue("Null", -1), -1);
   }
 }
index 2bd8dd0..fe70a98 100644 (file)
@@ -35,7 +35,8 @@ public class UrlDownloadClientTest {
   public void UrlDownloadTest()
   {
     UrlDownloadClient client = new UrlDownloadClient();
-    String urlstring = "http://identifiers.org/rest/collections/";
+    String urlstring = "http://www.jalview.org/services/identifiers";
+    // was "http://identifiers.org/rest/collections/";
     String outfile = "testfile.tmp";
 
     try
@@ -58,12 +59,14 @@ public class UrlDownloadClientTest {
 
     // download file has a believable size
     // identifiers.org file typically at least 250K
-    Assert.assertTrue(f.length() > 250000);
-
+    long n = f.length();
     if (f.exists())
     {
       f.delete();
     }
+    // 74589
+    Assert.assertTrue(n > 70000);
+
 
   }
 
diff --git a/unused/JalviewAppLoader.java b/unused/JalviewAppLoader.java
new file mode 100644 (file)
index 0000000..0f4833c
--- /dev/null
@@ -0,0 +1,27 @@
+package jalview.bin;
+
+import java.io.File;
+import java.net.URISyntaxException;
+
+import jalview.gui.AlignFrame;
+import jalview.io.BioJsHTMLOutput;
+import jalview.io.FileFormatI;
+import jalview.io.HtmlSvgOutput;
+import jalview.util.Platform;
+
+/**
+ * Abandoned -- see JalviewApp
+ * 
+ * A class to load parameters for either JalviewLite or Jalview
+ * 
+ * @author hansonr
+ *
+ */
+public class JalviewAppLoader
+{
+  
+  public JalviewAppLoader()
+  {
+  }
+
+}
\ No newline at end of file
diff --git a/unused/JalviewJSApp.java b/unused/JalviewJSApp.java
new file mode 100644 (file)
index 0000000..654569d
--- /dev/null
@@ -0,0 +1,12 @@
+package jalview.bin;
+
+import java.util.List;
+
+import jalview.appletgui.js.JSFunctionExec;
+
+public interface JalviewJSApp
+{
+
+
+
+}
similarity index 100%
rename from doc/github.css
rename to utils/doc/github.css
index e35111e..e60fe60 100644 (file)
@@ -321,7 +321,7 @@ sun/font/FontDesignMetrics.js
 sun/swing/DefaultLookup.js
 sun/swing/SwingLazyValue.js
 sun/text/resources/FormatData.js
-sun/text/resources/FormatData_en.js
+sun/text/resources/en/FormatData_en.js
 sun/util/resources/LocaleData.js
 swingjs/a2s/A2SContainer.js
 swingjs/a2s/A2SEvent.js
diff --git a/utils/jalviewjs/classlists/_jvjmol.txt b/utils/jalviewjs/classlists/_jvjmol.txt
new file mode 100644 (file)
index 0000000..bae181d
--- /dev/null
@@ -0,0 +1,232 @@
+jalview/ext/jmol/JalviewJmolBinding.js
+jalview/ext/jmol/JmolCommands.js
+jalview/ext/jmol/JmolParser.js
+jalview/gui/AppJmol.js
+jalview/gui/AppJmolBinding.js
+jalview/gui/StructureViewerBase.js
+jalview/io/StructureFile.js
+jalview/jbgui/GStructureViewer.js
+jalview/renderer/seqfeatures/FeatureColourFinder.js
+jalview/structure/StructureListener.js
+jalview/structure/StructureMapping.js
+jalview/structure/StructureMappingcommandSet.js
+jalview/structures/models/AAStructureBindingModel.js
+jalview/structures/models/SequenceStructureBindingModel.js
+jalview/util/CaseInsensitiveString.js
+jalview/util/HttpUtils.js
+jalview/util/MapList.js
+jalview/ws/dbsources/EbiFileRetrievedProxy.js
+jalview/ws/dbsources/Pdb.js
+jalview/ws/seqfetcher/DbSourceProxy.js
+jalview/ws/seqfetcher/DbSourceProxyImpl.js
+java/awt/font/TextAttribute.js
+java/awt/image/ImageProducer.js
+java/awt/image/PixelGrabber.js
+java/io/BufferedWriter.js
+java/io/FilterOutputStream.js
+java/io/OutputStream.js
+java/io/OutputStreamWriter.js
+java/io/PrintStream.js
+java/io/StringWriter.js
+java/io/Writer.js
+javajs/api/BytePoster.js
+javajs/api/GenericOutputChannel.js
+javajs/util/A4.js
+javajs/util/BS.js
+javajs/util/CU.js
+javajs/util/DF.js
+javajs/util/LimitedLineReader.js
+javajs/util/M3.js
+javajs/util/M34.js
+javajs/util/M4.js
+javajs/util/Measure.js
+javajs/util/OC.js
+javajs/util/P3.js
+javajs/util/P3i.js
+javajs/util/P4.js
+javajs/util/T3.js
+javajs/util/T3i.js
+javajs/util/T4.js
+javajs/util/V3.js
+mc_view/Atom.js
+mc_view/Bond.js
+mc_view/PDBChain.js
+mc_view/Residue.js
+org/jmol/adapter/readers/pdb/PdbReader.js
+org/jmol/adapter/smarter/Atom.js
+org/jmol/adapter/smarter/AtomIterator.js
+org/jmol/adapter/smarter/AtomSetCollection.js
+org/jmol/adapter/smarter/AtomSetCollectionReader.js
+org/jmol/adapter/smarter/AtomSetObject.js
+org/jmol/adapter/smarter/Bond.js
+org/jmol/adapter/smarter/BondIterator.js
+org/jmol/adapter/smarter/Resolver.js
+org/jmol/adapter/smarter/SmarterJmolAdapter.js
+org/jmol/adapter/smarter/Structure.js
+org/jmol/api/AtomIndexIterator.js
+org/jmol/api/EventManager.js
+org/jmol/api/GenericFileInterface.js
+org/jmol/api/GenericMouseInterface.js
+org/jmol/api/GenericPlatform.js
+org/jmol/api/Interface.js
+org/jmol/api/JmolAdapter.js
+org/jmol/api/JmolAdapterAtomIterator.js
+org/jmol/api/JmolAdapterBondIterator.js
+org/jmol/api/JmolCallbackListener.js
+org/jmol/api/JmolGraphicsInterface.js
+org/jmol/api/JmolPropertyManager.js
+org/jmol/api/JmolRendererInterface.js
+org/jmol/api/JmolRepaintManager.js
+org/jmol/api/JmolScriptEvaluator.js
+org/jmol/api/JmolScriptFunction.js
+org/jmol/api/JmolScriptManager.js
+org/jmol/api/JmolSelectionListener.js
+org/jmol/api/JmolStatusListener.js
+org/jmol/api/JmolViewer.js
+org/jmol/api/PlatformViewer.js
+org/jmol/api/Translator.js
+org/jmol/atomdata/AtomDataServer.js
+org/jmol/atomdata/RadiusData.js
+org/jmol/awt/AwtFile.js
+org/jmol/awt/AwtFont.js
+org/jmol/awt/Display.js
+org/jmol/awt/Image.js
+org/jmol/awt/Mouse.js
+org/jmol/awt/Platform.js
+org/jmol/bspt/Bspf.js
+org/jmol/bspt/Bspt.js
+org/jmol/bspt/CubeIterator.js
+org/jmol/bspt/Element.js
+org/jmol/bspt/Leaf.js
+org/jmol/bspt/Node.js
+org/jmol/c/CBK.js
+org/jmol/c/FIL.js
+org/jmol/c/PAL.js
+org/jmol/c/STER.js
+org/jmol/c/STR.js
+org/jmol/c/VDW.js
+org/jmol/g3d/CylinderRenderer.js
+org/jmol/g3d/G3DRenderer.js
+org/jmol/g3d/Graphics3D.js
+org/jmol/g3d/HermiteRenderer.js
+org/jmol/g3d/LineRenderer.js
+org/jmol/g3d/Pixelator.js
+org/jmol/g3d/Platform3D.js
+org/jmol/g3d/PrecisionRenderer.js
+org/jmol/g3d/SphereRenderer.js
+org/jmol/g3d/TextRenderer.js
+org/jmol/g3d/TextString.js
+org/jmol/g3d/TriangleRenderer.js
+org/jmol/i18n/GT.js
+org/jmol/i18n/Language.js
+org/jmol/i18n/Resource.js
+org/jmol/io/FileReader.js
+org/jmol/modelset/Atom.js
+org/jmol/modelset/AtomCollection.js
+org/jmol/modelset/AtomIteratorWithinModel.js
+org/jmol/modelset/Bond.js
+org/jmol/modelset/BondCollection.js
+org/jmol/modelset/BondIterator.js
+org/jmol/modelset/BondIteratorSelected.js
+org/jmol/modelset/Chain.js
+org/jmol/modelset/Group.js
+org/jmol/modelset/Model.js
+org/jmol/modelset/ModelLoader.js
+org/jmol/modelset/ModelSet.js
+org/jmol/modelset/Orientation.js
+org/jmol/modelset/Structure.js
+org/jmol/modelsetbio/AlphaMonomer.js
+org/jmol/modelsetbio/AlphaPolymer.js
+org/jmol/modelsetbio/BioModel.js
+org/jmol/modelsetbio/BioModelSet.js
+org/jmol/modelsetbio/BioPolymer.js
+org/jmol/modelsetbio/BioResolver.js
+org/jmol/modelsetbio/Helix.js
+org/jmol/modelsetbio/Monomer.js
+org/jmol/modelsetbio/ProteinStructure.js
+org/jmol/render/BallsRenderer.js
+org/jmol/render/FontLineShapeRenderer.js
+org/jmol/render/FrankRenderer.js
+org/jmol/render/RepaintManager.js
+org/jmol/render/ShapeRenderer.js
+org/jmol/render/SticksRenderer.js
+org/jmol/renderbio/BackboneRenderer.js
+org/jmol/renderbio/BioShapeRenderer.js
+org/jmol/renderbio/CartoonRenderer.js
+org/jmol/renderbio/RocketsRenderer.js
+org/jmol/renderbio/StrandsRenderer.js
+org/jmol/script/ContextToken.js
+org/jmol/script/SV.js
+org/jmol/script/ScriptCompiler.js
+org/jmol/script/ScriptContext.js
+org/jmol/script/ScriptError.js
+org/jmol/script/ScriptEval.js
+org/jmol/script/ScriptExpr.js
+org/jmol/script/ScriptFlowContext.js
+org/jmol/script/ScriptFunction.js
+org/jmol/script/ScriptManager.js
+org/jmol/script/ScriptMathProcessor.js
+org/jmol/script/ScriptParam.js
+org/jmol/script/ScriptTokenParser.js
+org/jmol/script/T.js
+org/jmol/scriptext/MathExt.js
+org/jmol/shape/AtomShape.js
+org/jmol/shape/Balls.js
+org/jmol/shape/Frank.js
+org/jmol/shape/Mesh.js
+org/jmol/shape/Shape.js
+org/jmol/shape/Sticks.js
+org/jmol/shapebio/Backbone.js
+org/jmol/shapebio/BioShape.js
+org/jmol/shapebio/BioShapeCollection.js
+org/jmol/shapebio/Cartoon.js
+org/jmol/shapebio/Rockets.js
+org/jmol/thread/HoverWatcherThread.js
+org/jmol/thread/JmolThread.js
+org/jmol/util/BSUtil.js
+org/jmol/util/BoxInfo.js
+org/jmol/util/C.js
+org/jmol/util/ColorEncoder.js
+org/jmol/util/CommandHistory.js
+org/jmol/util/DefaultLogger.js
+org/jmol/util/Edge.js
+org/jmol/util/Elements.js
+org/jmol/util/Escape.js
+org/jmol/util/GData.js
+org/jmol/util/Geodesic.js
+org/jmol/util/Int2IntHash.js
+org/jmol/util/Int2IntHashEntry.js
+org/jmol/util/Logger.js
+org/jmol/util/LoggerInterface.js
+org/jmol/util/MeshSurface.js
+org/jmol/util/Node.js
+org/jmol/util/Normix.js
+org/jmol/util/Point3fi.js
+org/jmol/util/Rectangle.js
+org/jmol/util/Rgb16.js
+org/jmol/util/Shader.js
+org/jmol/util/SimpleEdge.js
+org/jmol/util/SimpleNode.js
+org/jmol/util/TempArray.js
+org/jmol/viewer/ActionManager.js
+org/jmol/viewer/AnimationManager.js
+org/jmol/viewer/ColorManager.js
+org/jmol/viewer/FileManager.js
+org/jmol/viewer/Gesture.js
+org/jmol/viewer/GlobalSettings.js
+org/jmol/viewer/JC.js
+org/jmol/viewer/ModelManager.js
+org/jmol/viewer/MotionPoint.js
+org/jmol/viewer/MouseState.js
+org/jmol/viewer/PropertyManager.js
+org/jmol/viewer/SelectionManager.js
+org/jmol/viewer/ShapeManager.js
+org/jmol/viewer/StateManager.js
+org/jmol/viewer/StatusManager.js
+org/jmol/viewer/TransformManager.js
+org/jmol/viewer/Viewer.js
+org/jmol/viewer/binding/Binding.js
+org/jmol/viewer/binding/JmolBinding.js
+sun/awt/image/OffScreenImageSource.js
+sun/font/AttributeValues.js
+sun/font/EAttribute.js
\ No newline at end of file
diff --git a/utils/jalviewjs/site-resources/___j2sflags.htm b/utils/jalviewjs/site-resources/___j2sflags.htm
new file mode 100644 (file)
index 0000000..62a786d
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head><title>URL command line flags for SwingJS</title></head>
+<body><h3>URL command-line arguments for SwingJS</h3>
+<br>To enable these flags, simply add them after # or ? on your page.
+<br>The test is very simple -- just a case-sensitive string check. 
+<br>Separate them with &amp; For example:
+<br><a href="test_Test_Class.html?j2sverbose&j2snozcore&j2strace=applet">test_Test_Class.html?j2sverbose&j2snozcore&j2strace=applet</a>
+<br><br><table width=800 border=1 cellpadding=5>
+<tr><th colspan="2"></th></tr>
+<tr><td>j2sargs=a|b|c</td><td>arguments to be passed on to application.main(); use "|" to separate arguments. Overrides Info.args, which should be an array of strings, if present</td></tr>
+<tr><td>j2sdebugclip</td><td>shows all show/restore and clip operations in JSGraphics2D</td></tr>
+<tr><td>j2sdebugcode</td><td>deprecated; see j2snocore</td></tr>
+<tr><td>j2sdebugcore</td><td>deprecated; see j2snozcore</td></tr>
+<tr><td>j2sdebugpaint</td><td>report repaint manager information</td></tr>
+<tr><td>j2sevents</td><td>report ComponentEvent instances</td></tr>
+<tr><td>j2sfilter=xxx</td><td>remove messages with the specified text from System.out</td></tr>
+<tr><td>j2sheadless</td><td>run headlessly (must be a main()-based application or library without Swing or AWT)</td></tr>
+<tr><td>j2slang=en_US</td><td>default language for java.util.Locale (overrides Info.language)</td></tr>
+<tr><td>j2smouse</td><td>report mouse events other than mousemove</td></tr>
+<tr><td>j2smousemove</td><td>report all mouse events, including mousemove</td></tr>
+<tr><td>j2snocore</td><td>do not load core files (from j2s/core/)</td></tr>
+<tr><td>j2snoeval</td><td>use new Function() instead of eval(); breaks debugging, experimental</td></tr>
+<tr><td>j2snooutput</td><td>report only System.err message, not  System.out </td></tr>
+<tr><td>j2snozcore</td><td>use the uncompressed j2s/core/xxxcore.js files, not the compressed core.z.js files</td></tr>
+<tr><td>j2sprofile</td><td>track object creation; use J2S.getProfile() when you want a report; J2S.getProfile() or J2S.getProfile(nsec) to restart profiling anytime.</td></tr>
+<tr><td>j2sstrict</td><td>strict mode -- experimental</td></tr>
+<tr><td>j2strace=xxx or j2strace="xxx"</td><td>throw up an alert in the browser and a debugger statement in the developer whenever the specified text is found in System.out or System.err; if quotes are used, this must be an exact match to the entire output text (particularly useful when the message is something like "0", which otherwise would be next to impossible to find.</td></tr>
+<tr><td>j2sverbose</td><td>report all files loaded using AJAX</td></tr>
+</table>
+</body>
+</html>
@@ -5,6 +5,8 @@
 <script src="swingjs/swingjs2.js"></script>
 <script>
 
+// NOTE: The applet on this page is "Jalview". 
+
 // BH 2019.10.06 adds Tree and Pca functionality
 // BH see issue JAL-3451
 
@@ -24,12 +26,13 @@ Info = {
 jvGet = function(what) {
        switch(what) {
        case "tree":
-               testApplet.app.openTreePanel$jalview_gui_AlignFrame$S$S(null, "NJ","BLOSUM62")
+               Jalview.app.openTreePanel("NJ","BLOSUM62")
                break;
        case "pca":
-               testApplet.app.openPcaPanel$jalview_gui_AlignFrame$S(null, "BLOSUM62")
+               Jalview.app.openPcaPanel("BLOSUM62")
                break;
        case "3D":
+               Jalview.app.showStructure("1a70", "mmcif");
                break;
        }
        
@@ -37,7 +40,7 @@ jvGet = function(what) {
 
 $(document).ready(function() {
 
-  SwingJS.getApplet('testApplet', Info);
+  SwingJS.getApplet('Jalview', Info);
 
 });
 
@@ -60,7 +63,7 @@ The basic idea is that we have something interesting to say &mdash; some sort of
 across to our visitors with more than just text and images. The idea is to have a <b>dynamic</b> page that will involve <b>user interaction</b>. 
 <br><br>
 We start with a Jalview desktop. You can't see it, because I have placed it in a <code>div</code> tag with style <i>width:0px;height:0px</i> just after the period that ends this sentence.
-<div id="jalview-desktop-div" style="width:0px;height:0px;"></div>
+<div id="Jalview-desktop-div" style="width:0px;height:0px;"></div>
 <br>
 The idea is NOT to teach visitors how to use Jalview. The idea is to seamlessly integrate components of Jalview that can be used to enrich a discussion. 
 Like JSmol in <a target="_blank" href="http://proteopedia.org/wiki/index.php/Main_Page"><img src=https://pbs.twimg.com/profile_images/818051034/proteopedia_135x200_small_logo_for_Twitter_400x400.png width=16 height=16/>Proteopedia</a>.
@@ -71,7 +74,7 @@ See the big block of red color? That's the <i>Ferredoxin fold</i>domain.
 
 </div>
 </td><td style="background-color:lightgray;padding:20px">
-<div id="jalview-alignment-div" style="padding:20px;position:relative;top:0px;left:0px;width:680px;height:400px">
+<div id="Jalview-alignment-div" style="padding:20px;position:relative;top:0px;left:0px;width:680px;height:400px">
 <br><br>
 The alignment frame will appear here momentarily. When it does, you can go ahead and manipulate the alignment with your mouse.
 </div>
@@ -85,24 +88,27 @@ The alignment frame will appear here momentarily. When it does, you can go ahead
 <table style="background-color:lightgray"><tr><td  style="padding:0px 0px 0px 20px">
 <b>Select a few alignments</b> by left-dragging across a few rows of the alignment to make a selection box. 
 Then click one of the buttons below to see more information about your selected subset of the alignment.
-<ul>
-<li><button onclick='jvGet("tree")'>similarity tree</button></li>
-<li><button onclick='jvGet("pca")'>principal component analysis</button></li>
-</ul>
-
+<br><br>
+<button onclick='jvGet("tree")'>similarity tree</button> <button onclick='jvGet("pca")'>principal component analysis</button>
+<br><br>
+One more thing. Let's take a <a href="javascript:jvGet('3D')" style="text-decoration:none;color:red">look at the 3D structure</a> of one these proteins. Ferredoxins are important, because they have 
+iron-sulfur clusters that can accept and deliver electrons in metabolic processes. Let's see if we can find it. 
+<br><br>
 
 
 
 </td><td style="padding:0px 0px 0px 0px">
 <table style="border-spacing:0"><tr><td style="background-color:lightgray;padding:10px 0px 10px 20px">
-<div id="jalview-tree-div" style="position:relative;top:0px;left:0px;width:500px;height:500px">
+<div id="Jalview-tree-div" style="position:relative;top:0px;left:0px;width:500px;height:500px">
 <br><br><br><br>
-jalview-tree-div
+<a href="javascript:jvGet('tree')">Jalview-tree-div</a>
 </div>
 </td><td style="background-color:lightgray;padding:10px 20px 10px 0px">
-<div id="jalview-pca-div" style="position:relative;top:0px;left:0px;width:500px;height:500px">
+<div id="Jalview-pca-div" style="position:relative;top:0px;left:0px;width:500px;height:500px">
 <br><br><br><br>
-jalview-pca-div
+<a href="javascript:jvGet('pca')">Jalview-pca-div</a>
+<br><br>
+
 </div>
 </td></tr></table>
 
@@ -111,19 +117,23 @@ jalview-pca-div
 
 </td></tr>
 
+<!-- let it float
 <tr>
 
 <td valign=top style="padding:20px;height:650px" >
 
-<div id="jalview-structureviewer-div" style="position:relative;top:0px;left:0px;width:600px;height:600px">
-jalview-strucddtureviewer-div
-</div>
+<div id="Jalview-structureviewer-div" style="position:relative;top:0px;left:0px;width:600px;height:600px">
+<a href="javascript:jvGet('3D')">Jalview-structureviewer-div</a>
+</div> 
 </td>
 <td valign=top style="padding:20px;background-color:white" >
 One more thing. Let's take a look at the 3D structure of one these proteins. Ferredoxins are important, because they have 
 iron-sulfur clusters that can accept and deliver electrons in metabolic processes. Let's see if we can find it. 
 <br><center><button onclick='jvGet("3D")'>add the 3D structure</button>
-</td></tr></table>
+</td></tr>
+ -->
+
+</table>
 
 
 <!-- debugging (hidden) -->
diff --git a/utils/jalviewjs/site-resources/_jalview_embedded_example2.html b/utils/jalviewjs/site-resources/_jalview_embedded_example2.html
new file mode 100644 (file)
index 0000000..89a4f71
--- /dev/null
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Embedded JalviewJS Example 2</title><meta charset="utf-8" />
+<script src="swingjs/swingjs2.js"></script>
+<script>
+
+// NOTE: The applet on this page is "Jalview". 
+
+// BH 2019.10.06 adds Tree and Pca functionality
+// BH see issue JAL-3451
+
+if (!self.SwingJS)alert('swingjs2.js was not found. It needs to be in swingjs folder in the same directory as ' + document.location.href)
+Info = {
+  code: null,
+  main: "jalview.bin.JalviewJS2",
+  core: "NONE",
+//     core:"_jalview",
+
+       oninit:function() {$("#links").show();},
+       noannotation: true,
+       
+  readyFunction: null,
+       serverURL: 'https://chemapps.stolaf.edu/jmol/jsmol/php/jsmol.php',
+       j2sPath: 'swingjs/j2s',
+       console:'sysoutdiv',
+       allowjavascript: true
+}
+
+jvGet = function(what) {
+       switch(what) {
+       case "select1":
+               alert(Jalview1.getApp().getSelectedSequences());
+               break;
+       case "select2":
+               alert(Jalview2.getApp().getSelectedSequences());        
+               break;
+       case "select1fasta":
+               alert(Jalview1.getApp().getSelectedSequencesAsAlignment("fasta",true));
+               break;
+       case "select2fasta":
+               alert(Jalview2.getApp().getSelectedSequencesAsAlignment("fasta",true));
+               break;
+       }
+       
+}
+
+$(document).ready(function() {
+
+         SwingJS.getApplet('Jalview1', Info);
+         SwingJS.getApplet('Jalview2', Info);
+
+});
+
+</script>
+</head>
+<body style="background-image: url(images/coolVeryLightBG.png);">
+<table style="width:1000px;border:2px solid lightblue;border-spacing:0;font-size:16pt;" padding="10" valign="top">
+<tr>
+<td style="font-size:24;font-weight:bold;background-color:lightblue" colspan=2><center>Demonstration of embedded JalviewJS components</center>
+</td>
+
+
+</tr><tr>
+
+
+<td colspan=2 valign=top style="padding:20px;background-color:lightgray">
+this page tests two Jalview apps on the same page.
+<div id="Jalview1-desktop-div" style="width:0px;height:0px;"></div>
+<div id="Jalview2-desktop-div" style="width:0px;height:0px;"></div>
+</td></tr>
+<tr><td style="background-color:lightgray;padding:10px">
+<div id="Jalview1-alignment-div" style="padding:10px;position:relative;width:550px;height:300px">
+</td>
+<td style="background-color:lightgray;padding:10px">
+<div id="Jalview2-alignment-div" style="padding:10px;position:relative;width:550px;height:300px">
+</td>
+</tr>
+<tr><td>
+<div style="display:block;width:500px;height:300px;">
+<div id="sysoutdiv" style="border:1px solid green;width:100%;height:95%;overflow:auto"></div>
+This is System.out. <a href="javascript:J2S.thisApplet._clearConsole()">clear it</a> <br>Add ?j2snocore to URL to see full class list; ?j2sdebug to use uncompressed j2s/core files <br><a href="javascript:getClassList()">get _j2sClassList.txt</a>
+</div>
+
+</td><td valign=top>
+<div id=links style="display:none">
+<a href="javascript:jvGet('select1')">Show Jalview1 selections</a>
+
+<a href="javascript:jvGet('select2')">Show Jalview2 selections</a>
+
+<br>
+<a href="javascript:jvGet('select1fasta')">Show Jalview1 selections (fasta)</a>
+
+<a href="javascript:jvGet('select2fasta')">Show Jalview2 selections (fasta)</a>
+</div>
+</td></tr>
+</table>
+
+
+</body>
+</html>
index 7c20d44..d132460 100644 (file)
@@ -26,7 +26,7 @@ getClassList = function(){J2S._saveFile('_j2sclasslist.txt', Clazz.ClassFilesLoa
 </script>
 <div style="position:absolute;left:900px;top:30px;width:600px;height:300px;">
 <div id="sysoutdiv" contentEditable="true" style="border:1px solid green;width:100%;height:95%;overflow:auto"></div>
-This is System.out. <a href="javascript:testApplet._clearConsole()">clear it</a> <br>Add ?j2snocore to URL to see full class list; ?j2sdebug to use uncompressed j2s/core files <br><a href="javascript:getClassList()">get _j2sClassList.txt</a>
+This is System.out. <a href="javascript:testApplet._clearConsole()">clear it</a>  <a href='javascript:J2S.getProfile()'>start/stop profiling</a><br>see <a href=___j2sflags.htm>___j2sflags.htm</a> for SwingJS URL command-line options<br><a href="javascript:getClassList()">get _j2sClassList.txt</a>
 </div>
 </body>
 </html>