JAL-3269 JAL-3370 JalviewJS interface upgrade
authorhansonr <hansonr@STO24954W.ad.stolaf.edu>
Thu, 18 Jul 2019 06:15:38 +0000 (07:15 +0100)
committerhansonr <hansonr@STO24954W.ad.stolaf.edu>
Thu, 18 Jul 2019 06:15:38 +0000 (07:15 +0100)
12 files changed:
site-resources/images/coolJalviewBackgroundFading.png [new file with mode: 0644]
site-resources/images/coolVeryLightBG.png [new file with mode: 0644]
site-resources/jalview_embedded_example1.html [new file with mode: 0644]
src/jalview/analysis/scoremodels/SimilarityParams.java
src/jalview/bin/Jalview.java
src/jalview/bin/JalviewAppLoader.java
src/jalview/bin/JalviewJSApi.java [new file with mode: 0644]
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/CalculationChooser.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/SeqPanel.java

diff --git a/site-resources/images/coolJalviewBackgroundFading.png b/site-resources/images/coolJalviewBackgroundFading.png
new file mode 100644 (file)
index 0000000..0a6eb6c
Binary files /dev/null and b/site-resources/images/coolJalviewBackgroundFading.png differ
diff --git a/site-resources/images/coolVeryLightBG.png b/site-resources/images/coolVeryLightBG.png
new file mode 100644 (file)
index 0000000..ce32520
Binary files /dev/null and b/site-resources/images/coolVeryLightBG.png differ
diff --git a/site-resources/jalview_embedded_example1.html b/site-resources/jalview_embedded_example1.html
new file mode 100644 (file)
index 0000000..9151f33
--- /dev/null
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Embedded JalviewJS Example 1</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: null,
+  main: "jalview.bin.JalviewJS2",
+//  core: "NONE",
+       core:"_jalview",
+  readyFunction: null,
+       serverURL: 'https://chemapps.stolaf.edu/jmol/jsmol/php/jsmol.php',
+       j2sPath: 'swingjs/j2s',
+       console:'none',
+       allowjavascript: true,
+       
+       //Jalview-specific:
+       // note that desktop-frame-div has been set to display:none
+       jalview_SCREEN_WIDTH: 0, // desktop width -- 0 to hide
+       jalview_SCREEN_HEIGHT: 0,  // desktop height -- 0 to hide
+       jalview_SCREEN_X: 10,
+       jalview_SCREEN_Y: 10,
+       jalview_EMBEDDED: true
+       
+}
+
+jvGet = function(what) {
+       switch(what) {
+       case "tree":
+       break;
+       case "pca":
+       break;
+       }
+       
+}
+</script>
+</head>
+<body style="background-image: url(images/coolVeryLightBG.png);">
+<table style="width:1400px;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 valign=top style="padding:20px" rowspan=2>
+This simple page illustrates how one can embed the JalviewJS desktop into a web page. 
+The basic idea is that we have something interesting to say &mdash; some sort of scientific context &mdash; something we want to get 
+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>
+<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>.
+For example, in the space to the right, after a few seconds, you will see an alignment frame. This alignment is for 15 genes that code for one of the domains in the ferredoxin family (<a href=https://pfam.xfam.org/family/NIR_SIR_ferr target=_blank>NIR_SIR_ferr (PF03460)</a>). 
+<br><br>
+What you first see is just the first few bases. Doesn't look like much of an alignment, does it? But <b>scroll to the right</b>. 
+See the big block of red color? That's the <i>Ferredoxin fold</i>domain.
+<br><br>
+<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>
+
+</td><td style="background-color:lightgray;padding:20px">
+
+<div id="jalview-alignment-div" style="position:relative;top:0px;left:0px;width:600px;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>
+</td></tr>
+<tr>
+<!-- first column continued from above -->
+<td><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:300px;height:300px">
+<br><br><br><br>
+jalview-tree-div
+</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:300px;height:300px">
+<br><br><br><br>
+jalview-pca-div
+</div>
+</td></tr></table></td></tr>
+
+
+</table>
+
+<table>
+<tr><td>
+</td></tr><tr><td>
+</td></tr><tr>
+<td>
+<div id="jalview-structureviewer-div" style="position:relative;top:0px;left:0px;width:400px;height:400px">
+jalview-structureviewer-div
+</div>
+</td>
+<td>
+</td>
+<td>
+</td></tr></table>
+
+
+</tr></table>
+
+<script>
+SwingJS.getApplet('testApplet', Info)
+getClassList = function(){J2S._saveFile('_j2sclasslist.txt', Clazz.ClassFilesLoaded.sort().join('\n'))}
+</script>
+
+
+<div style="display:none;position:absolute;left:900px;top:30px;width:600px;height:300px;">
+<div id="sysoutdiv" 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>
+</div>
+</body>
+</html>
index 5c47703..69ca403 100644 (file)
@@ -104,6 +104,7 @@ public class SimilarityParams implements SimilarityParamsI
 
   private boolean denominateByShortestLength;
 
+
   /**
    * Constructor
    * 
@@ -124,6 +125,19 @@ public class SimilarityParams implements SimilarityParamsI
     denominateByShortestLength = shortestLength;
   }
 
+  /**
+   * BH added a non-Groovy "standard" set for JalviewJS
+   * 
+   * @param isPCA
+   */
+  public SimilarityParams(boolean isPCA)
+  {
+    includeGappedColumns = true;
+    matchGaps = !isPCA;
+    includeGaps = true;
+    denominateByShortestLength = false;
+  }
+
   @Override
   public boolean includeGaps()
   {
index 84fec45..047356b 100755 (executable)
@@ -34,6 +34,7 @@ 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;
@@ -51,7 +52,6 @@ import jalview.io.IdentifyFile;
 import jalview.io.NewickFile;
 import jalview.io.gff.SequenceOntologyFactory;
 import jalview.javascript.JSFunctionExec;
-import jalview.javascript.JalviewLiteJsApi;
 import jalview.javascript.MouseOverStructureListener;
 import jalview.renderer.seqfeatures.FeatureRenderer;
 import jalview.schemes.ColourSchemeI;
@@ -109,7 +109,7 @@ import netscape.javascript.JSObject;
  * @author $author$
  * @version $Revision$
  */
-public class Jalview implements ApplicationSingletonI, JalviewLiteJsApi
+public class Jalview implements ApplicationSingletonI, JalviewJSApi
 {
 
   public static Jalview getInstance()
@@ -142,7 +142,7 @@ public class Jalview implements ApplicationSingletonI, JalviewLiteJsApi
 
   public String appletResourcePath;
 
-  private JalviewAppLoader appLoader;
+  JalviewAppLoader appLoader;
 
   protected JSFunctionExec jsFunctionExec;
 
@@ -255,6 +255,27 @@ public class Jalview implements ApplicationSingletonI, JalviewLiteJsApi
     getInstance().doMain(args);
   }
 
+  /**
+   * 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
+    {
+      ArgsParser aparser = new ArgsParser(args);
+      return parseArguments(aparser, false);
+    } catch (Throwable t)
+    {
+      return t;
+    }
+  }
+
   private static void logClass(String name)
   {
     // BH - for event debugging in JavaScript
@@ -529,6 +550,15 @@ public class Jalview implements ApplicationSingletonI, JalviewLiteJsApi
       }
     }
 
+    parseArguments(aparser, true);
+  }
+
+  private Object parseArguments(ArgsParser aparser,
+          boolean isStartup)
+  {
+    boolean isJS = Platform.isJS();
+
+    Desktop desktop = (headless ? null : Desktop.getInstance());
     // script to execute after all loading is
     // completed one way or another
     // extract groovy argument and execute if necessary
@@ -855,6 +885,8 @@ public class Jalview implements ApplicationSingletonI, JalviewLiteJsApi
       }
       desktop.setInBatchMode(false);
     }
+
+    return null;
   }
 
   private boolean checkStartVamas(ArgsParser aparser)
@@ -938,7 +970,8 @@ public class Jalview implements ApplicationSingletonI, JalviewLiteJsApi
         // the Jalview specific remnants can now be imported into the new
         // session at the user's leisure.
         Cache.log.info(
-                "Skipping Push for import of data into existing vamsas session."); // TODO:
+                "Skipping Push for import of data into existing vamsas session.");
+        // TODO:
         // enable
         // this
         // when
@@ -1513,169 +1546,172 @@ public class Jalview implements ApplicationSingletonI, JalviewLiteJsApi
     appLoader.load(app);
   }
 
+  /**
+   * 
+   * @see jalview.bin.JalviewLiteJsApi#getSelectedSequences()
+   */
   @Override
   public String getSelectedSequences()
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getSelectedSequencesFrom(getCurrentAlignFrame());
   }
 
+  /**
+   * 
+   * @see jalview.bin.JalviewLiteJsApi#getSelectedSequences(java.lang.String)
+   */
   @Override
   public String getSelectedSequences(String sep)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getSelectedSequencesFrom(getCurrentAlignFrame(), sep);
   }
 
+  /**
+   * 
+   * @see jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
+   *      .AlignFrame)
+   */
   @Override
   public String getSelectedSequencesFrom(AlignFrameI alf)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getSelectedSequencesFrom(alf, null);
   }
 
+  /**
+   * 
+   * @see jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
+   *      .AlignFrame, java.lang.String)
+   */
   @Override
-  public String getSelectedSequencesFrom(AlignFrameI alf,
-          String sep)
+  public String getSelectedSequencesFrom(AlignFrameI alf, String sep)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return appLoader.getSelectedSequencesFrom(alf, sep);
   }
 
+  /**
+   * 
+   * @see jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
+   *      .AlignFrame, java.lang.String)
+   */
   @Override
   public void highlight(String sequenceId, String position,
           String alignedPosition)
   {
-    // TODO Auto-generated method stub
-
+    highlightIn(getCurrentAlignFrame(), sequenceId, position,
+            alignedPosition);
   }
 
   @Override
   public void highlightIn(AlignFrameI alf,
           String sequenceId, String position, String alignedPosition)
   {
-    // TODO Auto-generated method stub
-
+    appLoader.highlightIn(alf, sequenceId, position,
+            alignedPosition);
   }
 
   @Override
   public void select(String sequenceIds, String columns)
   {
-    // TODO Auto-generated method stub
-
+    selectIn(getCurrentAlignFrame(), sequenceIds, columns, null);
   }
 
   @Override
   public void select(String sequenceIds, String columns, String sep)
   {
-    // TODO Auto-generated method stub
-
+    selectIn(getCurrentAlignFrame(), sequenceIds, columns, sep);
   }
 
   @Override
   public void selectIn(AlignFrameI alf, String sequenceIds,
           String columns)
   {
-    // TODO Auto-generated method stub
-
+    selectIn(alf, sequenceIds, columns, null);
   }
 
   @Override
   public void selectIn(AlignFrameI alf, String sequenceIds,
           String columns, String sep)
   {
-    // TODO Auto-generated method stub
-
+    appLoader.selectIn(alf, sequenceIds, columns, sep);
   }
 
   @Override
   public String getSelectedSequencesAsAlignment(String format,
           String suffix)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getSelectedSequencesAsAlignmentFrom(getCurrentAlignFrame(),
+            format, suffix);
   }
 
   @Override
   public String getSelectedSequencesAsAlignmentFrom(
-          AlignFrameI alf, String format, String suffix)
+          AlignFrameI alf, String format, String sep)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return appLoader.getSelectedSequencesAsAlignmentFrom(alf, format, sep);
   }
 
   @Override
   public String getAlignmentOrder()
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getAlignmentFrom(getCurrentAlignFrame(), null);
   }
 
   @Override
   public String getAlignmentOrderFrom(AlignFrameI alf)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getAlignmentFrom(alf, null);
   }
 
   @Override
   public String getAlignmentOrderFrom(AlignFrameI alf,
           String sep)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return appLoader.getAlignmentOrderFrom(alf, sep);
   }
 
   @Override
   public String orderBy(String order, String undoName)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return orderBy(order, undoName, null);
   }
 
   @Override
   public String orderBy(String order, String undoName, String sep)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return orderAlignmentBy(getCurrentAlignFrame(), order, undoName, sep);
   }
 
   @Override
   public String orderAlignmentBy(AlignFrameI alf,
           String order, String undoName, String sep)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return appLoader.orderAlignmentBy(alf, order, undoName, sep);
   }
 
   @Override
   public String getAlignment(String format)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getAlignmentFrom(null, format, null);
   }
 
   @Override
   public String getAlignmentFrom(AlignFrameI alf,
           String format)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getAlignmentFrom(alf, format, null);
   }
 
   @Override
   public String getAlignment(String format, String suffix)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getAlignmentFrom(getCurrentAlignFrame(), format, suffix);
   }
 
   @Override
   public String getAlignmentFrom(AlignFrameI alf,
           String format, String suffix)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return appLoader.getAlignmentFrom(alf, format, suffix);
   }
 
   @Override
@@ -1769,8 +1805,8 @@ public class Jalview implements ApplicationSingletonI, JalviewLiteJsApi
   public AlignFrameI loadAlignment(String text,
           String title)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return appLoader.loadAlignment(text, AlignFrame.DEFAULT_WIDTH,
+            AlignFrame.DEFAULT_HEIGHT, title);
   }
 
   @Override
@@ -1830,97 +1866,118 @@ public class Jalview implements ApplicationSingletonI, JalviewLiteJsApi
   public boolean addPdbFile(AlignFrameI alFrame,
           String sequenceId, String pdbEntryString, String pdbFile)
   {
-    // TODO Auto-generated method stub
-    return false;
+    return appLoader.addPdbFile(alFrame, sequenceId, pdbEntryString,
+            pdbFile);
   }
 
   @Override
   public void scrollViewToIn(AlignFrameI alf,
           String topRow, String leftHandColumn)
   {
-    // TODO Auto-generated method stub
-
+    appLoader.scrollViewToIn(alf, topRow, leftHandColumn);
   }
 
   @Override
   public void scrollViewToRowIn(AlignFrameI alf,
           String topRow)
   {
-    // TODO Auto-generated method stub
-
+    appLoader.scrollViewToRowIn(alf, topRow);
   }
 
   @Override
   public void scrollViewToColumnIn(AlignFrameI alf,
           String leftHandColumn)
   {
-    // TODO Auto-generated method stub
-
+    appLoader.scrollViewToColumnIn(alf, leftHandColumn);
   }
 
   @Override
   public String getFeatureGroups()
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getFeatureGroupsOn(getCurrentAlignFrame());
   }
 
   @Override
   public String getFeatureGroupsOn(AlignFrameI alf)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return appLoader.getFeatureGroupsOn(alf);
   }
 
   @Override
   public String getFeatureGroupsOfState(boolean visible)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return getFeatureGroupsOfStateOn(getCurrentAlignFrame(), visible);
   }
 
   @Override
   public String getFeatureGroupsOfStateOn(AlignFrameI alf,
           boolean visible)
   {
-    // TODO Auto-generated method stub
-    return null;
+    return appLoader.getFeatureGroupsOfStateOn(alf, visible);
   }
 
   @Override
   public void setFeatureGroupStateOn(AlignFrameI alf,
           String groups, boolean state)
   {
-    // TODO Auto-generated method stub
-
+    setFeatureGroupStateOn(alf, groups, state);
   }
 
   @Override
   public void setFeatureGroupState(String groups, boolean state)
   {
-    // TODO Auto-generated method stub
-
+    appLoader.setFeatureGroupStateOn(getCurrentAlignFrame(), groups, state);
   }
 
   @Override
   public String getSeparator()
   {
-    // TODO Auto-generated method stub
-    return null;
+    return appLoader.getSeparator();
   }
 
   @Override
   public void setSeparator(String separator)
   {
-    // TODO Auto-generated method stub
-
+    appLoader.setSeparator(separator);
   }
 
   @Override
   public String getJsMessage(String messageclass, String viewId)
   {
-    // TODO Auto-generated method stub
+    // 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)
+  {
+    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)
+  {
+    return CalculationChooser.openPcaPanel(af, modelName, null);
+  }
+
 }
index 39e7c21..0d911c0 100644 (file)
@@ -1,12 +1,26 @@
 package jalview.bin;
 
+import jalview.api.AlignFrameI;
 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.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.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;
@@ -14,6 +28,8 @@ 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;
@@ -33,7 +49,19 @@ public class JalviewAppLoader
 
   private boolean debug;
 
-  private String separator;
+  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)
   {
@@ -730,4 +758,581 @@ public class JalviewAppLoader
     return arrayToSeparatorList(array, separator);
   }
 
+  public String getSelectedSequencesFrom(AlignFrameI alf, String sep)
+  {
+    StringBuffer result = new StringBuffer("");
+    if (sep == null || sep.length() == 0)
+    {
+      sep = separator; // "+0x00AC;
+    }
+    AlignViewport v = ((AlignFrame) 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 AlignFrameI alf,
+          final String groups, boolean state)
+  {
+    java.awt.EventQueue.invokeLater(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        ((AlignFrame) alf).setFeatureGroupState(
+                separatorListToArray(groups, separator), state);
+      }
+    });
+  }
+
+  public String getFeatureGroupsOfStateOn(AlignFrameI alf, boolean visible)
+  {
+    return arrayToSeparatorList(
+            ((AlignFrame) alf).getFeatureGroupsOfState(visible));
+  }
+
+  public void scrollViewToIn(final AlignFrameI alf, final String topRow,
+          final String leftHandColumn)
+  {
+    java.awt.EventQueue.invokeLater(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        try
+        {
+          ((AlignFrame) alf).scrollTo(new Integer(topRow).intValue(),
+                  new Integer(leftHandColumn).intValue());
+
+        } catch (Exception ex)
+        {
+          System.err.println("Couldn't parse integer arguments (topRow='"
+                  + topRow + "' and leftHandColumn='" + leftHandColumn
+                  + "')");
+          ex.printStackTrace();
+        }
+      }
+    });
+  }
+
+  public void scrollViewToRowIn(final AlignFrameI alf, final String topRow)
+  {
+
+    java.awt.EventQueue.invokeLater(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        try
+        {
+          ((AlignFrame) alf).scrollToRow(new Integer(topRow).intValue());
+
+        } catch (Exception ex)
+        {
+          System.err.println("Couldn't parse integer arguments (topRow='"
+                  + topRow + "')");
+          ex.printStackTrace();
+        }
+
+      }
+    });
+  }
+
+  public void scrollViewToColumnIn(final AlignFrameI alf,
+          final String leftHandColumn)
+  {
+    java.awt.EventQueue.invokeLater(new Runnable()
+    {
+
+      @Override
+      public void run()
+      {
+        try
+        {
+          ((AlignFrame) alf)
+                  .scrollToColumn(new Integer(leftHandColumn).intValue());
+
+        } catch (Exception ex)
+        {
+          System.err.println(
+                  "Couldn't parse integer arguments (leftHandColumn='"
+                          + leftHandColumn + "')");
+          ex.printStackTrace();
+        }
+      }
+    });
+
+  }
+
+  public boolean addPdbFile(AlignFrameI alf, String sequenceId,
+          String pdbEntryString, String pdbFile)
+  {
+    AlignFrame alFrame = (AlignFrame) 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 AlignFrameI 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(AlignFrameI alf)
+  {
+    return arrayToSeparatorList(
+            ((AlignFrame) alf).getFeatureGroups());
+  }
+
+  public void highlightIn(final AlignFrameI 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(
+            ((AlignFrame) alf).getViewport().getAlignment()
+                    .getSequencesArray());
+    final SequenceI sq = matcher.findIdMatch(sequenceId);
+    if (sq != null)
+    {
+      int apos = -1;
+      try
+      {
+        apos = new Integer(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 AlignFrameI 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 = ((AlignFrame) alf).getViewport().getAlignment();
+    jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
+            ((AlignFrame) 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 = new Integer(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 = new Integer(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 = new Integer(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()
+        {
+          ((AlignFrame) alf).select(sel, csel, ((AlignFrame) alf)
+                  .getCurrentView().getAlignment().getHiddenColumns());
+        }
+      });
+    }
+  }
+
+  public String getAlignmentOrderFrom(AlignFrameI alf, String sep)
+  {
+    AlignmentI alorder = ((AlignFrame) 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);
+  }
+
+  public String getSelectedSequencesAsAlignmentFrom(AlignFrameI alf,
+          String format, String suffix)
+  {
+    try
+    {
+      AlignViewport vp = ((AlignFrame) 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(AlignFrameI 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(
+              ((AlignFrame) 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 ((AlignFrame) alf).sortBy(aorder, _undoName) ? "true" : "";
+  }
+
+  public String getAlignmentFrom(AlignFrameI 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,
+              ((AlignFrame) alf).getViewport().getAlignment(), seqlimits);
+      return reply;
+    } catch (IllegalArgumentException ex)
+    {
+      ex.printStackTrace();
+      return "Error retrieving alignment, possibly invalid format specifier: "
+              + format;
+    }
+  }
+
 }
\ No newline at end of file
diff --git a/src/jalview/bin/JalviewJSApi.java b/src/jalview/bin/JalviewJSApi.java
new file mode 100644 (file)
index 0000000..61850a4
--- /dev/null
@@ -0,0 +1,41 @@
+package jalview.bin;
+
+import jalview.gui.AlignFrame;
+import jalview.javascript.JalviewLiteJsApi;
+
+/**
+ * JAL-3369 JalviewJS API BH 2019.07.17
+ * 
+ * @author hansonr
+ *
+ */
+public interface JalviewJSApi extends JalviewLiteJsApi
+{
+
+  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
+   * @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
+   * @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);
+
+}
index 0067a6b..386c39b 100644 (file)
@@ -33,6 +33,7 @@ import jalview.api.AlignViewControllerGuiI;
 import jalview.api.AlignViewControllerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
+//from JalviewLite imports import jalview.api.FeatureRenderer;
 import jalview.api.FeatureSettingsControllerI;
 import jalview.api.SplitContainerI;
 import jalview.api.ViewStyleI;
@@ -5789,6 +5790,67 @@ public class AlignFrame extends GAlignFrame
 
   }
 
+  /**
+   * BH 2019 from JalviewLite
+   * 
+   * get sequence feature groups that are hidden or shown
+   * 
+   * @param visible
+   *          true is visible
+   * @return list
+   */
+  public String[] getFeatureGroupsOfState(boolean visible)
+  {
+    jalview.api.FeatureRenderer fr = null;
+    if (alignPanel != null
+            && (fr = alignPanel
+                    .getFeatureRenderer()) != null)
+    {
+      List<String> gps = fr.getGroups(visible);
+      String[] _gps = gps.toArray(new String[gps.size()]);
+      return _gps;
+    }
+    return null;
+  }
+
+  public void scrollTo(int row, int column)
+  {
+    alignPanel.getSeqPanel().scrollTo(row, column);
+  }
+
+  public void scrollToRow(int row)
+  {
+    alignPanel.getSeqPanel().scrollToRow(row);
+  }
+
+  public void scrollToColumn(int column)
+  {
+    alignPanel.getSeqPanel().scrollToColumn(column);
+  }
+
+  /**
+   * 
+   * @return list of feature groups on the view
+   */
+  public String[] getFeatureGroups()
+  {
+    jalview.api.FeatureRenderer fr = null;
+    if (alignPanel != null
+            && (fr = alignPanel.getFeatureRenderer()) != null)
+    {
+      List<String> gps = fr.getFeatureGroups();
+      String[] _gps = gps.toArray(new String[gps.size()]);
+      return _gps;
+    }
+    return null;
+  }
+
+  public void select(SequenceGroup sel, ColumnSelection csel,
+          HiddenColumns hidden)
+  {
+    alignPanel.getSeqPanel().selection(sel, csel, hidden, null);
+  }
+
 }
 
 class PrintThread extends Thread
index fa30e09..f723794 100644 (file)
@@ -1730,4 +1730,109 @@ public class AlignmentPanel extends GAlignmentPanel implements
     return seqPanel.seqCanvas.getSequenceRenderer();
   }
 
+  public boolean scrollTo(int ostart, int end, int seqIndex,
+          boolean scrollToNearest, boolean redrawOverview)
+  {
+    int startv, endv, starts, ends;// , width;
+
+    int start = -1;
+    if (av.hasHiddenColumns())
+    {
+      AlignmentI al = av.getAlignment();
+      start = al.getHiddenColumns().absoluteToVisibleColumn(ostart);
+      end = al.getHiddenColumns().absoluteToVisibleColumn(end);
+      if (start == end)
+      {
+        if (!scrollToNearest && !al.getHiddenColumns().isVisible(ostart))
+        {
+          // don't scroll - position isn't visible
+          return false;
+        }
+      }
+    }
+    else
+    {
+      start = ostart;
+    }
+
+    ViewportRanges ranges = av.getRanges();
+    if (!av.getWrapAlignment())
+    {
+      /*
+       * int spos=av.getStartRes(),sqpos=av.getStartSeq(); if ((startv =
+       * av.getStartRes()) >= start) { spos=start-1; // seqIn //
+       * setScrollValues(start - 1, seqIndex); } else if ((endv =
+       * av.getEndRes()) <= end) { // setScrollValues(spos=startv + 1 + end -
+       * endv, seqIndex); spos=startv + 1 + end - endv; } else if ((starts =
+       * av.getStartSeq()) > seqIndex) { setScrollValues(av.getStartRes(),
+       * seqIndex); } else if ((ends = av.getEndSeq()) <= seqIndex) {
+       * setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1); }
+       */
+
+      // below is scrolling logic up to Jalview 2.8.2
+      // if ((av.getStartRes() > end)
+      // || (av.getEndRes() < start)
+      // || ((av.getStartSeq() > seqIndex) || (av.getEndSeq() < seqIndex)))
+      // {
+      // if (start > av.getAlignment().getWidth() - hextent)
+      // {
+      // start = av.getAlignment().getWidth() - hextent;
+      // if (start < 0)
+      // {
+      // start = 0;
+      // }
+      //
+      // }
+      // if (seqIndex > av.getAlignment().getHeight() - vextent)
+      // {
+      // seqIndex = av.getAlignment().getHeight() - vextent;
+      // if (seqIndex < 0)
+      // {
+      // seqIndex = 0;
+      // }
+      // }
+      // setScrollValues(start, seqIndex);
+      // }
+      // logic copied from jalview.gui.AlignmentPanel:
+      if ((startv = ranges.getStartRes()) >= start)
+      {
+        /*
+         * Scroll left to make start of search results visible
+         */
+        setScrollValues(start - 1, seqIndex);
+      }
+      else if ((endv = ranges.getEndRes()) <= end)
+      {
+        /*
+         * Scroll right to make end of search results visible
+         */
+        setScrollValues(startv + 1 + end - endv, seqIndex);
+      }
+      else if ((starts = ranges.getStartSeq()) > seqIndex)
+      {
+        /*
+         * Scroll up to make start of search results visible
+         */
+        setScrollValues(ranges.getStartRes(), seqIndex);
+      }
+      else if ((ends = ranges.getEndSeq()) <= seqIndex)
+      {
+        /*
+         * Scroll down to make end of search results visible
+         */
+        setScrollValues(ranges.getStartRes(), starts + seqIndex - ends + 1);
+      }
+      /*
+       * Else results are already visible - no need to scroll
+       */
+    }
+    else
+    {
+      ranges.scrollToWrappedVisible(start);
+    }
+
+    paintAlignment(redrawOverview, false);
+    return true;
+  }
+
 }
index 45bf2d7..13506a5 100644 (file)
@@ -62,8 +62,13 @@ import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
 /**
- * A dialog where a user can choose and action Tree or PCA calculation options
+ * A dialog where a user can choose and action Tree or PCA calculation options.
+ * 
+ * Allows also for dialog-free static methods openPCAPanel(...) and
+ * openTreePanel(...) for scripted use.
+ * 
  */
+@SuppressWarnings("serial")
 public class CalculationChooser extends JPanel
 {
   /*
@@ -74,7 +79,7 @@ public class CalculationChooser extends JPanel
    */
   private static boolean treeMatchGaps = true;
 
-  private static final Font VERDANA_11PT = new Font("Verdana", 0, 11);
+  private static Font VERDANA_11PT;
 
   private static final int MIN_TREE_SELECTION = 3;
 
@@ -102,7 +107,7 @@ public class CalculationChooser extends JPanel
 
   private JCheckBox shorterSequence;
 
-  final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer();
+  private static ComboBoxTooltipRenderer renderer; // BH was not static
 
   List<String> tips = new ArrayList<>();
 
@@ -112,6 +117,37 @@ public class CalculationChooser extends JPanel
   private PCAPanel pcaPanel;
 
   /**
+   * 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 if successful; the string
+   *         "label.you_need_at_least_n_sequences" if number of sequences
+   *         selected is inappropriate
+   */
+  public static Object openTreePanel(AlignFrame af, String treeType,
+          String modelName)
+  {
+    return 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
+   */
+  public static Object openPcaPanel(AlignFrame af, String modelName)
+  {
+    return openPcaPanel(af, modelName, null);
+  }
+
+  /**
    * Constructor
    * 
    * @param af
@@ -232,6 +268,10 @@ public class CalculationChooser extends JPanel
     paramsPanel.add(includeGappedColumns);
     paramsPanel.add(shorterSequence);
 
+    if (VERDANA_11PT == null)
+    {
+      VERDANA_11PT = new Font("Verdana", 0, 11);
+    }
     /*
      * OK / Cancel buttons
      */
@@ -380,7 +420,11 @@ public class CalculationChooser extends JPanel
    */
   protected JComboBox<String> buildModelOptionsList()
   {
-    final JComboBox<String> scoreModelsCombo = new JComboBox<>();
+    JComboBox<String> scoreModelsCombo = new JComboBox<>();
+    if (renderer == null)
+    {
+      renderer = new ComboBoxTooltipRenderer();
+    }
     scoreModelsCombo.setRenderer(renderer);
 
     /*
@@ -538,6 +582,63 @@ public class CalculationChooser extends JPanel
    */
   protected void openTreePanel(String modelName, SimilarityParamsI params)
   {
+    Object ret = openTreePanel(af,
+            neighbourJoining.isSelected() ? TreeBuilder.NEIGHBOUR_JOINING
+                    : TreeBuilder.AVERAGE_DISTANCE,
+            modelName, params);
+    if (ret instanceof String)
+    {
+      JvOptionPane.showMessageDialog(this, // was opening on Desktop?
+              MessageManager.formatMessage(
+                      (String) ret,
+                      MIN_TREE_SELECTION),
+              MessageManager.getString("label.not_enough_sequences"),
+              JvOptionPane.WARNING_MESSAGE);
+
+    }
+  }
+
+  /**
+   * Open a new PCA panel on the desktop
+   * 
+   * @param modelName
+   * @param params
+   */
+  protected void openPcaPanel(String modelName, SimilarityParamsI params)
+  {
+    Object ret = openPcaPanel(af, modelName, params);
+    if (ret instanceof String)
+    {
+      JvOptionPane.showInternalMessageDialog(this,
+              MessageManager.formatMessage(
+                      (String) ret,
+                      MIN_PCA_SELECTION),
+              MessageManager
+                      .getString("label.sequence_selection_insufficient"),
+              JvOptionPane.WARNING_MESSAGE);
+    }
+    else
+    {
+      // only used for test suite
+      pcaPanel = (PCAPanel) ret;
+    }
+
+  }
+
+  /**
+   * Open a new Tree panel on the desktop statically
+   * 
+   * @param af
+   * @param treeType
+   * @param modelName
+   * @param params
+   * @return null, or the string "label.you_need_at_least_n_sequences" if number
+   *         of sequences selected is inappropriate
+   */
+  public static Object openTreePanel(AlignFrame af, String treeType,
+          String modelName, SimilarityParamsI params)
+  {
+
     /*
      * gui validation shouldn't allow insufficient sequences here, but leave
      * this check in in case this method gets exposed programmatically in future
@@ -546,56 +647,58 @@ public class CalculationChooser extends JPanel
     SequenceGroup sg = viewport.getSelectionGroup();
     if (sg != null && sg.getSize() < MIN_TREE_SELECTION)
     {
-      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
-              MessageManager.formatMessage(
-                      "label.you_need_at_least_n_sequences",
-                      MIN_TREE_SELECTION),
-              MessageManager.getString("label.not_enough_sequences"),
-              JvOptionPane.WARNING_MESSAGE);
-      return;
+      return "label.you_need_at_least_n_sequences";
+    }
+
+    if (params == null)
+    {
+      params = getSimilarityParameters(false);
     }
 
-    String treeType = neighbourJoining.isSelected()
-            ? TreeBuilder.NEIGHBOUR_JOINING
-            : TreeBuilder.AVERAGE_DISTANCE;
     af.newTreePanel(treeType, modelName, params);
+    return null;
   }
 
   /**
-   * Open a new PCA panel on the desktop
+   * public static method for JalviewJS API
    * 
+   * @param af
    * @param modelName
    * @param params
+   * @return the PCAPanel, or null if number of sequences selected is
+   *         inappropriate
    */
-  protected void openPcaPanel(String modelName, SimilarityParamsI params)
+  public static Object openPcaPanel(AlignFrame af, String modelName,
+          SimilarityParamsI params)
   {
+
     AlignViewport viewport = af.getViewport();
 
     /*
      * gui validation shouldn't allow insufficient sequences here, but leave
      * this check in in case this method gets exposed programmatically in future
+     * 
+     * 
      */
     if (((viewport.getSelectionGroup() != null)
             && (viewport.getSelectionGroup().getSize() < MIN_PCA_SELECTION)
             && (viewport.getSelectionGroup().getSize() > 0))
             || (viewport.getAlignment().getHeight() < MIN_PCA_SELECTION))
     {
-      JvOptionPane.showInternalMessageDialog(this,
-              MessageManager.formatMessage(
-                      "label.you_need_at_least_n_sequences",
-                      MIN_PCA_SELECTION),
-              MessageManager
-                      .getString("label.sequence_selection_insufficient"),
-              JvOptionPane.WARNING_MESSAGE);
-      return;
+      return "label.you_need_at_least_n_sequences";
+    }
+
+    if (params == null)
+    {
+      params = getSimilarityParameters(true);
     }
 
     /*
      * construct the panel and kick off its calculation thread
      */
-    pcaPanel = new PCAPanel(af.alignPanel, modelName, params);
-    new Thread(pcaPanel).start();
-
+    PCAPanel pcap = new PCAPanel(af.alignPanel, modelName, params);
+    new Thread(pcap).start();
+    return pcap;
   }
 
   /**
@@ -611,6 +714,7 @@ public class CalculationChooser extends JPanel
     }
   }
 
+
   /**
    * Returns a data bean holding parameters for similarity (or distance) model
    * calculation
@@ -618,7 +722,8 @@ public class CalculationChooser extends JPanel
    * @param doPCA
    * @return
    */
-  protected SimilarityParamsI getSimilarityParameters(boolean doPCA)
+  public static SimilarityParamsI getSimilarityParameters(
+          boolean doPCA)
   {
     // commented out: parameter choices read from gui widgets
     // SimilarityParamsI params = new SimilarityParams(
@@ -639,6 +744,7 @@ public class CalculationChooser extends JPanel
 
     return new SimilarityParams(includeGapGap, matchGap, includeGapResidue,
             matchOnShortestLength);
+
   }
 
   /**
@@ -656,6 +762,7 @@ public class CalculationChooser extends JPanel
 
   public PCAPanel getPcaPanel()
   {
+    // only called for FreeUpMemoryTest
     return pcaPanel;
   }
 }
index 0553fef..2c54906 100644 (file)
@@ -30,6 +30,7 @@ import java.awt.Color;
  */
 public class FeatureRenderer
         extends jalview.renderer.seqfeatures.FeatureRenderer
+        implements jalview.api.FeatureRenderer
 {
   Color resBoxColour;
 
index 2e0c1f4..de67c39 100644 (file)
@@ -2770,4 +2770,45 @@ public class SeqPanel extends JPanel
   {
     return lastSearchResults;
   }
+
+  /**
+   * scroll to the given row/column - or nearest visible location
+   * 
+   * @param row
+   * @param column
+   */
+  public void scrollTo(int row, int column)
+  {
+
+    row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
+    column = column < 0 ? ap.av.getRanges().getStartRes() : column;
+    ap.scrollTo(column, column, row, true, true);
+  }
+
+  /**
+   * scroll to the given row - or nearest visible location
+   * 
+   * @param row
+   */
+  public void scrollToRow(int row)
+  {
+
+    row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
+    ap.scrollTo(ap.av.getRanges().getStartRes(),
+            ap.av.getRanges().getStartRes(), row, true, true);
+  }
+
+  /**
+   * scroll to the given column - or nearest visible location
+   * 
+   * @param column
+   */
+  public void scrollToColumn(int column)
+  {
+
+    column = column < 0 ? ap.av.getRanges().getStartRes() : column;
+    ap.scrollTo(column, column, ap.av.getRanges().getStartSeq(), true,
+            true);
+  }
+
 }