JAL-3379 JalviewJS-API specs - preliminary
[jalview.git] / src / jalview / api / JalviewJSApp.java
1 package jalview.api;
2
3 import java.awt.EventQueue;
4 //import java.applet.AppletContext;
5 import java.io.IOException;
6 import java.net.URL;
7 import java.util.ArrayList;
8 import java.util.Hashtable;
9 import java.util.List;
10 import java.util.StringTokenizer;
11 import java.util.Vector;
12
13 import jalview.bin.ArgsParser;
14 import jalview.bin.Jalview;
15 import jalview.bin.JalviewJSApi;
16 import jalview.datamodel.Alignment;
17 import jalview.datamodel.AlignmentI;
18 import jalview.datamodel.AlignmentOrder;
19 import jalview.datamodel.ColumnSelection;
20 import jalview.datamodel.HiddenColumns;
21 import jalview.datamodel.PDBEntry;
22 import jalview.datamodel.Sequence;
23 import jalview.datamodel.SequenceGroup;
24 import jalview.datamodel.SequenceI;
25 import jalview.gui.AlignFrame;
26 import jalview.gui.AlignViewport;
27 import jalview.gui.AlignmentPanel;
28 import jalview.gui.CalculationChooser;
29 import jalview.gui.Desktop;
30 import jalview.gui.StructureViewer;
31 import jalview.io.AnnotationFile;
32 import jalview.io.AppletFormatAdapter;
33 import jalview.io.DataSourceType;
34 import jalview.io.FeaturesFile;
35 import jalview.io.FileFormat;
36 import jalview.io.FileFormatI;
37 import jalview.io.FileFormats;
38 import jalview.io.IdentifyFile;
39 import jalview.io.JPredFile;
40 import jalview.io.JnetAnnotationMaker;
41 import jalview.io.NewickFile;
42 import jalview.renderer.seqfeatures.FeatureRenderer;
43 import jalview.structure.SelectionListener;
44 import jalview.structure.SelectionSource;
45 import jalview.structure.StructureSelectionManager;
46 import jalview.structure.VamsasSource;
47 import jalview.util.HttpUtils;
48 import jalview.util.MessageManager;
49 import jalview.util.Platform;
50
51 //import netscape.javascript.JSObject;
52
53 /**
54  * Basically the JalviewLite application, but without JalviewLite
55  * 
56  * @author hansonr
57  *
58  */
59 public class JalviewJSApp implements JalviewJSApi
60 {
61   private ArgsParser aparser;
62
63   private boolean debug;
64
65   private String[] ret = new String[1];
66
67   private String separator = "\u00AC"; // JalviewLite note: the default used to
68                                        // be '|', but many sequence IDS include
69                                        // pipes.
70
71
72   public JalviewJSApp(ArgsParser aparser)
73   {
74     this.aparser = aparser;
75     Platform.setAppClass(this);
76   }
77
78
79   public void load(AlignFrame af)
80   {
81     String sep = (String) getAppletParameter("separator", true);
82     if (sep != null)
83     {
84       if (sep.length() > 0)
85       {
86         separator = sep;
87       }
88       else
89       {
90         throw new Error(MessageManager
91                 .getString("error.invalid_separator_parameter"));
92       }
93     }
94
95     loadTree(af);
96     loadScoreFile();
97     loadFeatures(af);
98     loadAnnotations(af);
99     loadJnetFile(af);
100     loadPdbFiles(af);
101   }
102
103   // TODO BH 2019
104   //
105   // These are methods that are in JalviewLite that various classes call
106   // but are not in JalviewLiteJsApi. Or, even if they are, other classes
107   // call
108   // them to JalviewLite directly. Some may not be necessary, but they have
109   // to
110   // be at least mentioned here, or the classes calling them should
111   // reference
112   // JalviewLite itself.
113
114   // private boolean alignPDBStructures; // From JalviewLite; not implemented
115   //
116   
117   @Override
118   public String getParameter(String name)
119   {
120     return (String) getAppletParameter(name, true);
121   }
122
123   @Override
124   public Object getAppletParameter(String name, boolean asString)
125   {
126     return aparser.getAppletValue(name, null, asString);
127   }
128
129   /**
130    * Get the applet-like code base even though this is an application.
131    */
132
133   @Override
134   public URL getCodeBase()
135   {
136     return Platform.getCodeBase();
137   }
138
139   /**
140    * Get the applet-like document base even though this is an application.
141    */
142
143   @Override
144   public URL getDocumentBase()
145   {
146     return Platform.getDocumentBase();
147   }
148
149   @Override
150   public Object getFrameForSource(VamsasSource source)
151   {
152     if (source != null)
153     {
154       AlignFrame af;
155       if (source instanceof jalview.gui.AlignViewport
156               && source == (af = Jalview.getCurrentAlignFrame())
157                       .getViewport())
158       {
159         // should be valid if it just generated an event!
160         return af;
161       }
162       // TODO: ensure that if '_af' is specified along with a handler
163       // function, then only events from that alignFrame are sent to that
164       // function
165     }
166     return null;
167   }
168
169   @Override
170   public FeatureRenderer getNewFeatureRenderer(AlignViewportI vp)
171   {
172     return new jalview.gui.FeatureRenderer((AlignmentPanel) vp);
173   }
174
175   @Override
176   public Object[] getSelectionForListener(SequenceGroup seqsel,
177           ColumnSelection colsel, HiddenColumns hidden,
178           SelectionSource source, Object alignFrame)
179   {
180     return getSelectionForListener(null, seqsel, colsel, hidden, source,
181             alignFrame);
182   }
183
184   /**
185    * scorefile
186    * 
187    */
188
189   @Override
190   public boolean loadScoreFile(String sScoreFile) throws IOException
191   {
192     return loadScoreFile(null, sScoreFile);
193   }
194   
195   public boolean loadScoreFile(AlignFrame af, String sScoreFile) throws IOException
196   {
197     (af == null ? Jalview.getCurrentAlignFrame() : af).loadJalviewDataFile(sScoreFile, null, null, null);
198     return true;
199   }
200
201   public void loadTree(AlignFrame af, NewickFile nf, String treeFile) throws IOException
202   {
203     if (af == null)
204       af = Jalview.getCurrentAlignFrame();
205     af.getViewport()
206             .setCurrentTree(af.showNewickTree(nf, treeFile).getTree());
207   }
208
209   @Override
210   public void newFeatureSettings()
211   {
212     System.err.println(
213             "Jalview applet interface newFeatureSettings not implemented");
214   }
215
216   //
217   //
218   // public void setAlignPdbStructures(boolean defaultParameter)
219   // {
220   // alignPDBStructures = true;
221   // }
222   //
223
224   @Override
225   public void newStructureView(PDBEntry pdb, SequenceI[] seqs,
226           String[] chains, DataSourceType protocol)
227   {
228     newStructureView(null, pdb, seqs, chains, protocol);
229     
230   }
231
232   public void newStructureView(AlignFrame af, PDBEntry pdb,
233           SequenceI[] seqs, String[] chains, DataSourceType protocol)
234   {
235     StructureViewer.launchStructureViewer(
236             (af == null ? Jalview.getCurrentAlignFrame() : af).alignPanel,
237             pdb, seqs);
238   }
239
240   /**
241    * features
242    * @param af 
243    * 
244    */
245
246   @Override
247   public boolean parseFeaturesFile(String filename, DataSourceType protocol)
248   {
249     return parseFeaturesFile(null, filename, protocol);
250   }
251   
252   /**
253    * @j2sAlias parseFeatureFile
254    * 
255    * @param af
256    * @param filename
257    * @param protocol
258    * @return
259    */
260   public boolean parseFeaturesFile(AlignFrame af, String filename, DataSourceType protocol)
261   {
262     return (af == null ? Jalview.getCurrentAlignFrame() : af).parseFeaturesFile(filename, protocol);
263   }
264
265   /**
266    * annotations, jpredfile, jnetfile
267    * 
268    */
269
270   @Override
271   public void updateForAnnotations()
272   {
273     updateForAnnotations(null);
274   }
275
276   public void updateForAnnotations(AlignFrame af)
277   {
278     (af == null ? Jalview.getCurrentAlignFrame() : af).updateForAnnotations();
279   }
280
281   @Override
282   public boolean addPdbFile(AlignFrame alf, String sequenceId,
283           String pdbEntryString, String pdbFile)
284   {
285     if (alf == null)
286     {
287       alf = Jalview.getCurrentAlignFrame();
288     }
289     SequenceI toaddpdb = alf.getViewport().getAlignment()
290             .findName(sequenceId);
291     boolean needtoadd = false;
292     if (toaddpdb != null)
293     {
294       Vector<PDBEntry> pdbe = toaddpdb.getAllPDBEntries();
295       PDBEntry pdbentry = null;
296       if (pdbe != null && pdbe.size() > 0)
297       {
298         for (int pe = 0, peSize = pdbe.size(); pe < peSize; pe++)
299         {
300           pdbentry = pdbe.elementAt(pe);
301           if (!pdbentry.getId().equals(pdbEntryString)
302                   && !pdbentry.getFile().equals(pdbFile))
303           {
304             pdbentry = null;
305           }
306           else
307           {
308             continue;
309           }
310         }
311       }
312       if (pdbentry == null)
313       {
314         pdbentry = new PDBEntry();
315         pdbentry.setId(pdbEntryString);
316         pdbentry.setFile(pdbFile);
317         needtoadd = true; // add this new entry to sequence.
318       }
319       // resolve data source
320       // TODO: this code should be a refactored to an io package
321       DataSourceType protocol = AppletFormatAdapter.resolveProtocol(pdbFile,
322               FileFormat.PDB);
323       if (protocol == null)
324       {
325         return false;
326       }
327       if (needtoadd)
328       {
329         pdbentry.setProperty("protocol", protocol);
330         toaddpdb.addPDBId(pdbentry);
331         alf.alignPanel.getStructureSelectionManager()
332                 .registerPDBEntry(pdbentry);
333       }
334     }
335     return true;
336   }
337
338   @Override
339   public String arrayToSeparatorList(String[] array)
340   {
341     return arrayToSeparatorList(array, separator);
342   }
343
344   /**
345    * concatenate the list with separator
346    * 
347    * @param list
348    * @param separator
349    * @return concatenated string
350    */
351   public static String arrayToSeparatorList(String[] list, String separator)
352   {
353     // TODO use StringUtils version
354     StringBuffer v = new StringBuffer();
355     if (list != null && list.length > 0)
356     {
357       for (int i = 0, iSize = list.length; i < iSize; i++)
358       {
359         if (list[i] != null)
360         {
361           if (i > 0)
362           {
363             v.append(separator);
364           }
365           v.append(list[i]);
366         }
367       }
368       // if (debug)
369       // {
370       // System.err
371       // .println("Returning '" + separator + "' separated List:\n");
372       // System.err.println(v);
373       // }
374       return v.toString();
375     }
376     // if (debug)
377     // {
378     // System.err.println(
379     // "Returning empty '" + separator + "' separated List\n");
380     // }
381     return "" + separator;
382   }
383
384   @Override
385   public String getAlignment(String format)
386   {
387     return getAlignmentFrom(null, format, null);
388   }
389
390   /**
391    * suffix string "true"/"false" (default true)
392    *          passed to AlnFile class controls whether /START-END is added to
393    *          sequence names
394    */
395   @Override
396   public String getAlignment(String format, String suffix)
397   {
398     return getAlignmentFrom(Jalview.getCurrentAlignFrame(), format, suffix);
399   }
400
401   @Override
402   public String getAlignmentFrom(AlignFrame alf, String format)
403   {
404     return getAlignmentFrom(alf, format, null);
405   }
406
407   @Override
408   public String getAlignmentFrom(AlignFrame alf, String format,
409           String suffix)
410   {
411     try
412     {
413       if (alf == null)
414       {
415         alf = Jalview.getCurrentAlignFrame();
416       }
417       boolean seqlimits = (suffix == null
418               || suffix.equalsIgnoreCase("true"));
419
420       FileFormatI theFormat = FileFormats.getInstance().forName(format);
421       String reply = new AppletFormatAdapter().formatSequences(theFormat,
422               alf.getViewport().getAlignment(), seqlimits);
423       return reply;
424     } catch (IllegalArgumentException ex)
425     {
426       ex.printStackTrace();
427       return "Error retrieving alignment, possibly invalid format specifier: "
428               + format;
429     }
430   }
431
432   @Override
433   public String getAlignmentOrder()
434   {
435     return getAlignmentFrom(Jalview.getCurrentAlignFrame(), null);
436   }
437
438   @Override
439   public String getAlignmentOrderFrom(AlignFrame alf)
440   {
441     return getAlignmentFrom(alf, null);
442   }
443
444   @Override
445   public String getAlignmentOrderFrom(AlignFrame alf, String sep)
446   {
447     if (alf == null)
448     {
449       alf = Jalview.getCurrentAlignFrame();
450     }
451     AlignmentI alorder = alf.getViewport().getAlignment();
452     String[] order = new String[alorder.getHeight()];
453     for (int i = 0; i < order.length; i++)
454     {
455       order[i] = alorder.getSequenceAt(i).getName();
456     }
457     return arrayToSeparatorList(order, sep);
458   }
459
460   @Override
461   public String getAnnotation()
462   {
463     return getAnnotationFrom(null);
464   }
465
466   @Override
467   public String getAnnotationFrom(AlignFrame alf)
468   {
469     if (alf == null)
470     {
471       alf = Jalview.getCurrentAlignFrame();
472     }
473     String annotation = new AnnotationFile()
474             .printAnnotationsForView(alf.getViewport());
475     return annotation;
476   }
477
478   @Override
479   public String getFeatureGroups()
480   {
481     return getFeatureGroupsOn(null);
482   }
483
484   @Override
485   public String getFeatureGroupsOfState(boolean visible)
486   {
487     return getFeatureGroupsOfStateOn(null, visible);
488   }
489
490   @Override
491   public String getFeatureGroupsOfStateOn(AlignFrame alf, boolean visible)
492   {
493     if (alf == null)
494     {
495       alf = Jalview.getCurrentAlignFrame();
496     }
497     return arrayToSeparatorList(alf.getFeatureGroupsOfState(visible));
498   }
499
500   @Override
501   public String getFeatureGroupsOn(AlignFrame alf)
502   {
503     if (alf == null)
504     {
505       alf = Jalview.getCurrentAlignFrame();
506     }
507     return arrayToSeparatorList(alf.getFeatureGroups());
508   }
509
510   @Override
511   public String getFeatures(String format)
512   {
513     return getFeaturesFrom(null, format);
514   }
515
516   /**
517    * JavaScript interface to print the alignment frame
518    * 
519    * @param alf
520    * @param format
521    *          "jalview" or "gff" with or without ";includeComplement" or
522    *          ";includeNonpositional"; default with no ";" is
523    *          ";includeNonpositional"
524    * @return
525    */
526   @Override
527   public String getFeaturesFrom(AlignFrame alf, String format)
528   {
529     if (alf == null)
530     {
531       alf = Jalview.getCurrentAlignFrame();
532     }
533     String features;
534     FeaturesFile formatter = new FeaturesFile();
535     format = format.toLowerCase();
536     if (format.indexOf(";") < 0)
537       format += ";includenonpositional";
538     boolean nonpos = format.indexOf(";includenonpositional") > 0;
539     boolean compl = format.indexOf(";includecomplement") >= 0;
540     if (format.startsWith("jalview"))
541     {
542       features = formatter.printJalviewFormat(
543               alf.getViewport().getAlignment().getSequencesArray(),
544               alf.alignPanel.getFeatureRenderer(), nonpos, compl);
545     }
546     else
547     {
548       features = formatter.printGffFormat(
549               alf.getViewport().getAlignment().getSequencesArray(),
550               alf.alignPanel.getFeatureRenderer(), nonpos, compl);
551     }
552
553     if (features == null)
554     {
555       features = "";
556     }
557     return features;
558
559   }
560   
561   /**
562    * read sequence1...sequenceN as a raw alignment
563    * 
564    * @param jalviewApp
565    * @return
566    */
567   public String getPastedSequence(JalviewJSApp jalviewApp)
568   {
569     StringBuffer data = new StringBuffer("PASTE");
570     int i = 1;
571     String file = null;
572     while ((file = (String) getAppletParameter("sequence" + i,
573             true)) != null)
574     {
575       data.append(file.toString() + "\n");
576       i++;
577     }
578     if (data.length() > 5)
579     {
580       file = data.toString();
581     }
582     return file;
583   }
584
585   /**
586    * 
587    * @see jalview.appletgui.js.JalviewLiteJsApi#getSelectedSequences()
588    */
589   @Override
590   public SequenceI[] getSelectedSequences()
591   {
592     return getSelectedSequencesFrom(Jalview.getCurrentAlignFrame());
593   }
594
595   /**
596    * 
597    * @see jalview.appletgui.js.JalviewLiteJsApi#getSelectedSequences(java.lang.String)
598    */
599   @Override
600   public SequenceI[] getSelectedSequences(String sep)
601   {
602     return getSelectedSequencesFrom(Jalview.getCurrentAlignFrame(), sep);
603   }
604
605   @Override
606   public String getSelectedSequencesAsAlignment(String format,
607           String suffix)
608   {
609     return getSelectedSequencesAsAlignmentFrom(null, format, suffix);
610   }
611
612   @Override
613   public String getSelectedSequencesAsAlignmentFrom(AlignFrame alf,
614           String format, String suffix)
615   {
616
617     if (alf == null)
618     {
619       alf = Jalview.getCurrentAlignFrame();
620     }
621
622     boolean seqlimits = (suffix == null || suffix.equalsIgnoreCase("true"));
623     try
624     {
625       AlignViewport vp = alf.getViewport();
626       FileFormatI theFormat = FileFormats.getInstance().forName(format);
627       if (vp.getSelectionGroup() != null)
628       {
629         // JBPNote: getSelectionAsNewSequence behaviour has changed - this
630         // method now returns a full copy of sequence data
631         // TODO consider using getSequenceSelection instead here
632         String reply = new AppletFormatAdapter().formatSequences(theFormat,
633                 new Alignment(vp.getSelectionAsNewSequence()), seqlimits);
634         return reply;
635       }
636     } catch (IllegalArgumentException ex)
637     {
638       ex.printStackTrace();
639       return "Error retrieving alignment, possibly invalid format specifier: "
640               + format;
641     }
642     return "";
643   }
644
645   /**
646    * 
647    * @see jalview.appletgui.js.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
648    *      .AlignFrame)
649    */
650   @Override
651   public SequenceI[] getSelectedSequencesFrom(AlignFrame alf)
652   {
653     return getSelectedSequencesFrom(alf, null);
654   }
655
656   @Override
657   public SequenceI[] getSelectedSequencesFrom(AlignFrame alf, String sep)
658   {
659     if (alf == null)
660     {
661       alf = Jalview.getCurrentAlignFrame();
662     }
663     AlignViewport v = alf.getViewport();
664     if (v.getSelectionGroup() != null)
665     {
666       return v.getSelectionGroup()
667               .getSequencesInOrder(v.getAlignment());
668     }
669
670     return null;
671   }
672
673   public Object[] getSelectionForListener(AlignFrame alf,
674           SequenceGroup seqsel, ColumnSelection colsel,
675           HiddenColumns hidden, SelectionSource source, Object alignFrame)
676   {
677     if (alf == null)
678     {
679       alf = Jalview.getCurrentAlignFrame();
680     }
681     // System.err.println("Testing selection event relay to
682     // jsfunction:"+_listener);
683     String setid = "";
684     AlignFrame src = (AlignFrame) alignFrame;
685     if (source != null)
686     {
687       if (source instanceof AlignViewport && alf.getViewport() == source)
688       {
689         // should be valid if it just generated an event!
690         src = alf;
691
692       }
693     }
694     String[] seqs = new String[] {};
695     String[] cols = new String[] {};
696     int strt = 0, end = (src == null) ? -1
697             : src.alignPanel.av.getAlignment().getWidth();
698     if (seqsel != null && seqsel.getSize() > 0)
699     {
700       seqs = new String[seqsel.getSize()];
701       for (int i = 0; i < seqs.length; i++)
702       {
703         seqs[i] = seqsel.getSequenceAt(i).getName();
704       }
705       if (strt < seqsel.getStartRes())
706       {
707         strt = seqsel.getStartRes();
708       }
709       if (end == -1 || end > seqsel.getEndRes())
710       {
711         end = seqsel.getEndRes();
712       }
713     }
714     if (colsel != null && !colsel.isEmpty())
715     {
716       if (end == -1)
717       {
718         end = colsel.getMax() + 1;
719       }
720       cols = new String[colsel.getSelected().size()];
721       for (int i = 0; i < cols.length; i++)
722       {
723         cols[i] = "" + (1 + colsel.getSelected().get(i).intValue());
724       }
725     }
726     else
727     {
728       if (seqsel != null && seqsel.getSize() > 0)
729       {
730         // send a valid range, otherwise we send the empty selection
731         cols = new String[2];
732         cols[0] = "" + (1 + strt) + "-" + (1 + end);
733       }
734     }
735     return new Object[] { src, setid, arrayToSeparatorList(seqs),
736         arrayToSeparatorList(cols) };
737   }
738
739   @Override
740   public String getSeparator()
741   {
742     return separator;
743   }
744
745   /**
746    * 
747    * @see jalview.appletgui.js.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
748    *      .AlignFrame, java.lang.String)
749    */
750   @Override
751   public void highlight(String sequenceId, String position,
752           String alignedPosition)
753   {
754     highlightIn(null, sequenceId, position, alignedPosition);
755   }
756
757   @Override
758   public void highlightIn(AlignFrame alf, final String sequenceId,
759           final String position, final String alignedPosition)
760   {
761     if (alf == null)
762     {
763       alf = Jalview.getCurrentAlignFrame();
764     }
765     // TODO: could try to highlight in all alignments if alf==null
766     jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
767             alf.getViewport().getAlignment().getSequencesArray());
768     final SequenceI sq = matcher.findIdMatch(sequenceId);
769     if (sq != null)
770     {
771       int apos = -1;
772       try
773       {
774         apos = Integer.valueOf(position).intValue();
775         apos--;
776       } catch (NumberFormatException ex)
777       {
778         return;
779       }
780       final int pos = apos;
781       // use vamsas listener to broadcast to all listeners in scope
782       if (alignedPosition != null && (alignedPosition.trim().length() == 0
783               || alignedPosition.toLowerCase().indexOf("false") > -1))
784       {
785         java.awt.EventQueue.invokeLater(new Runnable()
786         {
787           @Override
788           public void run()
789           {
790             StructureSelectionManager
791                     .getStructureSelectionManager(Desktop.getInstance())
792                     .mouseOverVamsasSequence(sq, sq.findIndex(pos), null);
793           }
794         });
795       }
796       else
797       {
798         java.awt.EventQueue.invokeLater(new Runnable()
799         {
800           @Override
801           public void run()
802           {
803             StructureSelectionManager
804                     .getStructureSelectionManager(Desktop.getInstance())
805                     .mouseOverVamsasSequence(sq, pos, null);
806           }
807         });
808       }
809     }
810   }
811
812   public AlignFrame loadAlignment(String text, int width, int height,
813           String title)
814   {
815     AlignmentI al = null;
816
817     try
818     {
819       FileFormatI format = new IdentifyFile().identify(text,
820               DataSourceType.PASTE);
821       al = new AppletFormatAdapter().readFile(text, DataSourceType.PASTE,
822               format);
823       if (al.getHeight() > 0)
824       {
825         return new AlignFrame(al, width, height, title);
826       }
827     } catch (IOException ex)
828     {
829       ex.printStackTrace();
830     }
831     return null;
832   }
833
834   // public void setMouseoverListener(String listener)
835   // {
836   // appLoader.setMouseoverListener(listener);
837   // }
838   //
839   //
840   // public void setMouseoverListener(AlignFrame af, String listener)
841   // {
842   // }
843   //
844
845   @Override
846   public AlignFrame loadAlignment(String text, String title)
847   {
848     return loadAlignment(text, AlignFrame.DEFAULT_WIDTH,
849             AlignFrame.DEFAULT_HEIGHT, title);
850   }
851
852   @Override
853   public void loadAnnotation(String annotation)
854   {
855     loadAnnotationFrom(null, annotation);
856   }
857
858   @Override
859   public void loadAnnotationFrom(AlignFrame alf, String annotation)
860   {
861     if (alf == null)
862     {
863       alf = Jalview.getCurrentAlignFrame();
864     }
865     if (new AnnotationFile().annotateAlignmentView(alf.getViewport(),
866             annotation, DataSourceType.PASTE))
867     {
868       alf.alignPanel.fontChanged();
869       alf.alignPanel.setScrollValues(0, 0);
870     }
871     else
872     {
873       alf.parseFeaturesFile(annotation, DataSourceType.PASTE);
874     }
875   }
876
877   /**
878    * Load annotations if specified by parameter. Returns true if loaded, else
879    * false.
880    * 
881    * 
882    * @param alignFrame
883    * @return
884    */
885   protected boolean loadAnnotations(AlignFrame af)
886   {
887     boolean result = false;
888     String param = (String) getAppletParameter("annotations", true);
889     if (param != null)
890     {
891       ret[0] = param;
892       DataSourceType protocol = resolveFileProtocol(ret);
893       param = ret[0];
894       if (new AnnotationFile().annotateAlignmentView(af.getViewport(), param,
895               protocol))
896       {
897         updateForAnnotations();
898         result = true;
899       }
900       else
901       {
902         System.err
903                 .println("Annotations were not added from annotation file '"
904                         + param + "'");
905       }
906     }
907     return result;
908   }
909
910   //// JalviewJSApi
911
912   /**
913    * Load features file and view settings as specified by parameters. Returns
914    * true if features were loaded, else false.
915    * @param  
916    * 
917    * @param alignFrame
918    * @return
919    */
920   protected boolean loadFeatures(AlignFrame af)
921   {
922     boolean result = false;
923     // ///////////////////////////
924     // modify display of features
925     // we do this before any features have been loaded, ensuring any hidden
926     // groups are hidden when features first displayed
927     //
928     // hide specific groups
929     //
930     String param = (String) getAppletParameter("hidefeaturegroups", true);
931     if (param != null)
932     {
933       setFeatureGroupState(af, separatorListToArray(param, separator), false);
934       // setFeatureGroupStateOn(newAlignFrame, param, false);
935     }
936     // show specific groups
937     param = (String) getAppletParameter("showfeaturegroups", true);
938     if (param != null)
939     {
940       setFeatureGroupState(af, separatorListToArray(param, separator), true);
941       // setFeatureGroupStateOn(newAlignFrame, param, true);
942     }
943     // and now load features
944     param = (String) getAppletParameter("features", true);
945     if (param != null)
946     {
947       ret[0] = param;
948       DataSourceType protocol = resolveFileProtocol(ret);
949
950       result = parseFeaturesFile(af, ret[0], protocol);
951     }
952
953     param = (String) getAppletParameter("showFeatureSettings", true);
954     if (param != null && param.equalsIgnoreCase("true"))
955     {
956       newFeatureSettings();
957     }
958     return result;
959   }
960
961   @Override
962   public void loadFeatures(String features, boolean autoenabledisplay)
963   {
964     loadFeaturesFrom(null, features, autoenabledisplay);
965   }
966
967   @Override
968   public boolean loadFeaturesFrom(AlignFrame alf, String features,
969           boolean autoenabledisplay)
970   {
971     if (alf == null)
972     {
973       alf = Jalview.getCurrentAlignFrame();
974     }
975     boolean ret = alf.parseFeaturesFile(features, DataSourceType.PASTE);
976     if (!ret)
977     {
978       return false;
979     }
980     if (autoenabledisplay)
981     {
982       alf.getViewport().setShowSequenceFeatures(true);
983       // this next was for a checkbox in JalviewLite
984       // ((AlignFrame) alf).getViewport().sequenceFeatures.setState(true);
985     }
986     return true;
987   }
988
989   /**
990    * Load in a Jnetfile if specified by parameter. Returns true if loaded, else
991    * false.
992    * 
993    * @param alignFrame
994    * @return
995    */
996   protected boolean loadJnetFile(AlignFrame af)
997   {
998     boolean result = false;
999     String param = (String) getAppletParameter("jnetfile", true);
1000     if (param == null)
1001     {
1002       // jnet became jpred around 2016
1003       param = (String) getAppletParameter("jpredfile", true);
1004     }
1005     if (param != null)
1006     {
1007       try
1008       {
1009         ret[0] = param;
1010         DataSourceType protocol = resolveFileProtocol(ret);
1011         JPredFile predictions = new JPredFile(ret[0], protocol);
1012         JnetAnnotationMaker.add_annotation(predictions,
1013                 af.getViewport().getAlignment(), 0, false);
1014         // false == do not add sequence profile from concise output
1015         af.getViewport().getAlignment().setupJPredAlignment();
1016         updateForAnnotations();
1017         result = true;
1018       } catch (Exception ex)
1019       {
1020         ex.printStackTrace();
1021       }
1022     }
1023     return result;
1024   }
1025
1026   /**
1027    * Load PDBFiles if any specified by parameter(s). Returns true if loaded,
1028    * else false.
1029    * 
1030    * @param loaderFrame
1031    * @return
1032    */
1033   protected boolean loadPdbFiles(AlignFrame af)
1034   {
1035     boolean result = false;
1036     /*
1037      * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6 -
1038      * related to JAL-434
1039      */
1040
1041     // not supported (as for JalviewLite)
1042     // boolean doAlign = false;//"true".equalsIgnoreCase("" +
1043     // getAppletParameter("alignpdbfiles", false));
1044     // setAlignPdbStructures(doAlign);
1045     /*
1046      * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
1047      * PDB|1GAQ|1GAQ|C">
1048      * 
1049      * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
1050      * 
1051      * <param name="PDBfile3" value="1q0o Q45135_9MICO">
1052      */
1053
1054     // Accumulate pdbs here if they are heading for the same view (if
1055     // alignPdbStructures is true)
1056     // ArrayList<Object[]> pdbs = new ArrayList<>();
1057     // create a lazy matcher if we're asked to
1058     boolean relaxed = "true".equalsIgnoreCase(
1059             "" + getAppletParameter("relaxedidmatch", false));
1060     jalview.analysis.SequenceIdMatcher matcher = relaxed
1061             ? new jalview.analysis.SequenceIdMatcher(
1062                     af.getViewport().getAlignment().getSequencesArray())
1063             : null;
1064
1065     int pdbFileCount = 0;
1066     String param;
1067     do
1068     {
1069       if (pdbFileCount > 0)
1070       {
1071         param = (String) getAppletParameter("PDBFILE" + pdbFileCount, true);
1072       }
1073       else
1074       {
1075         param = (String) getAppletParameter("PDBFILE", true);
1076       }
1077
1078       if (param != null)
1079       {
1080         PDBEntry pdb = new PDBEntry();
1081
1082         String seqstring;
1083         SequenceI[] seqs = null;
1084         String[] chains = null;
1085
1086         StringTokenizer st = new StringTokenizer(param, " ");
1087
1088         if (st.countTokens() < 2)
1089         {
1090           String sequence = (String) getAppletParameter("PDBSEQ", true);
1091           if (sequence != null)
1092           {
1093             seqs = new SequenceI[] { matcher == null
1094                     ? (Sequence) af.getViewport().getAlignment()
1095                             .findName(sequence)
1096                     : matcher.findIdMatch(sequence) };
1097           }
1098
1099         }
1100         else
1101         {
1102           param = st.nextToken();
1103           List<SequenceI> tmp = new ArrayList<>();
1104           List<String> tmp2 = new ArrayList<>();
1105
1106           while (st.hasMoreTokens())
1107           {
1108             seqstring = st.nextToken();
1109             StringTokenizer st2 = new StringTokenizer(seqstring, "=");
1110             if (st2.countTokens() > 1)
1111             {
1112               // This is the chain
1113               tmp2.add(st2.nextToken());
1114               seqstring = st2.nextToken();
1115             }
1116             tmp.add(matcher == null
1117                     ? (Sequence) af.getViewport().getAlignment()
1118                             .findName(seqstring)
1119                     : matcher.findIdMatch(seqstring));
1120           }
1121
1122           seqs = tmp.toArray(new SequenceI[tmp.size()]);
1123           if (tmp2.size() == tmp.size())
1124           {
1125             chains = tmp2.toArray(new String[tmp2.size()]);
1126           }
1127         }
1128         pdb.setId(param);
1129         ret[0] = param;
1130         DataSourceType protocol = resolveFileProtocol(ret);
1131         // TODO check JAL-357 for files in a jar (CLASSLOADER)
1132         pdb.setFile(ret[0]);
1133
1134         if (seqs != null)
1135         {
1136           for (int i = 0; i < seqs.length; i++)
1137           {
1138             if (seqs[i] != null)
1139             {
1140               ((Sequence) seqs[i]).addPDBId(pdb);
1141               StructureSelectionManager
1142                       .getStructureSelectionManager(
1143                               (StructureSelectionManagerProvider) this)
1144                       .registerPDBEntry(pdb);
1145             }
1146             else
1147             {
1148               if (debug)
1149               {
1150                 // this may not really be a problem but we give a warning
1151                 // anyway
1152                 System.err.println(
1153                         "Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "
1154                                 + i + ")");
1155               }
1156             }
1157           }
1158
1159           // if (doAlign)
1160           // {
1161           // pdbs.add(new Object[] { pdb, seqs, chains, protocol });
1162           // }
1163           // else
1164           {
1165             newStructureView(af, pdb, seqs, chains, protocol);
1166           }
1167         }
1168       }
1169
1170       pdbFileCount++;
1171     } while (param != null || pdbFileCount < 10);
1172     //
1173     // if (doAlign && pdbs.size() > 0)
1174     // {
1175     // SequenceI[][] seqs = new SequenceI[pdbs.size()][];
1176     // PDBEntry[] pdb = new PDBEntry[pdbs.size()];
1177     // String[][] chains = new String[pdbs.size()][];
1178     // String[] protocols = new String[pdbs.size()];
1179     // for (int pdbsi = 0, pdbsiSize = pdbs
1180     // .size(); pdbsi < pdbsiSize; pdbsi++)
1181     // {
1182     // Object[] o = pdbs.get(pdbsi);
1183     // pdb[pdbsi] = (PDBEntry) o[0];
1184     // seqs[pdbsi] = (SequenceI[]) o[1];
1185     // chains[pdbsi] = (String[]) o[2];
1186     // protocols[pdbsi] = (String) o[3];
1187     // }
1188     //// alignedStructureView(pdb, seqs, chains, protocols);
1189     // result = true;
1190     // }
1191     return result;
1192   }
1193
1194   /**
1195    * Load a score file if specified by parameter. Returns true if file was
1196    * loaded, else false.
1197    * 
1198    * @param loaderFrame
1199    */
1200   protected boolean loadScoreFile()
1201   {
1202     boolean result = false;
1203     String sScoreFile = (String) getAppletParameter("scoreFile", true);
1204     if (sScoreFile != null && !"".equals(sScoreFile))
1205     {
1206       try
1207       {
1208         if (debug)
1209         {
1210           System.err.println(
1211                   "Attempting to load T-COFFEE score file from the scoreFile parameter");
1212         }
1213         result = loadScoreFile(sScoreFile);
1214         if (!result)
1215         {
1216           System.err.println(
1217                   "Failed to parse T-COFFEE parameter as a valid score file ('"
1218                           + sScoreFile + "')");
1219         }
1220       } catch (Exception e)
1221       {
1222         System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
1223                 sScoreFile, e.getMessage());
1224       }
1225     }
1226     return result;
1227   }
1228
1229   /**
1230    * Load a tree for the alignment if specified by parameter. Returns true if a
1231    * tree was loaded, else false.
1232    * 
1233    * @return
1234    */
1235   protected boolean loadTree(AlignFrame af)
1236   {
1237     boolean result = false;
1238     String treeFile = (String) getAppletParameter("tree", true);
1239     if (treeFile == null)
1240     {
1241       treeFile = (String) getAppletParameter("treefile", true);
1242     }
1243
1244     if (treeFile != null)
1245     {
1246       try
1247       {
1248         ret[0] = treeFile;
1249         NewickFile fin = new NewickFile(treeFile, resolveFileProtocol(ret));
1250         fin.parse();
1251
1252         if (fin.getTree() != null)
1253         {
1254           loadTree(af, fin, ret[0]);
1255           result = true;
1256           if (debug)
1257           {
1258             System.out.println("Successfully imported tree.");
1259           }
1260         }
1261         else
1262         {
1263           if (debug)
1264           {
1265             System.out.println(
1266                     "Tree parameter did not resolve to a valid tree.");
1267           }
1268         }
1269       } catch (Exception ex)
1270       {
1271         ex.printStackTrace();
1272       }
1273     }
1274     return result;
1275   }
1276
1277   /**
1278    * public static method for JalviewJS API to open a PCAPanel without
1279    * necessarily using a dialog.
1280    * 
1281    * @param af
1282    * @param modelName
1283    * @return the PCAPanel, or the string "label.you_need_at_least_n_sequences"
1284    *         if number of sequences selected is inappropriate
1285    */
1286   @Override
1287   public Object openPcaPanel(AlignFrame af, String modelName)
1288   {
1289     if (af == null)
1290     {
1291       af = Jalview.getCurrentAlignFrame();
1292     }
1293     return CalculationChooser.openPcaPanel(af, modelName, null);
1294   }
1295
1296   /**
1297    * Open a new Tree panel on the desktop statically. Params are standard (not
1298    * set by Groovy). No dialog is opened.
1299    * 
1300    * @param af
1301    * @param treeType
1302    * @param modelName
1303    * @return null, or the string "label.you_need_at_least_n_sequences" if number
1304    *         of sequences selected is inappropriate
1305    */
1306   @Override
1307   public Object openTreePanel(AlignFrame af, String treeType,
1308           String modelName)
1309   {
1310     if (af == null)
1311     {
1312       af = Jalview.getCurrentAlignFrame();
1313     }
1314     return CalculationChooser.openTreePanel(af, treeType, modelName, null);
1315   }
1316
1317   @Override
1318   public String orderAlignmentBy(AlignFrame alf, String order,
1319           String undoName, String sep)
1320   {
1321     if (sep == null || sep.length() == 0)
1322     {
1323       sep = separator;
1324     }
1325     String[] ids = separatorListToArray(order, sep);
1326     SequenceI[] sqs = null;
1327     if (ids != null && ids.length > 0)
1328     {
1329       if (alf == null)
1330       {
1331         alf = Jalview.getCurrentAlignFrame();
1332       }
1333       jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
1334               alf.getViewport().getAlignment().getSequencesArray());
1335       int s = 0;
1336       sqs = new SequenceI[ids.length];
1337       for (int i = 0; i < ids.length; i++)
1338       {
1339         if (ids[i].trim().length() == 0)
1340         {
1341           continue;
1342         }
1343         SequenceI sq = matcher.findIdMatch(ids[i]);
1344         if (sq != null)
1345         {
1346           sqs[s++] = sq;
1347         }
1348       }
1349       if (s > 0)
1350       {
1351         SequenceI[] sqq = new SequenceI[s];
1352         System.arraycopy(sqs, 0, sqq, 0, s);
1353         sqs = sqq;
1354       }
1355       else
1356       {
1357         sqs = null;
1358       }
1359     }
1360     if (sqs == null)
1361     {
1362       return "";
1363     }
1364     ;
1365     final AlignmentOrder aorder = new AlignmentOrder(sqs);
1366
1367     if (undoName != null && undoName.trim().length() == 0)
1368     {
1369       undoName = null;
1370     }
1371     final String _undoName = undoName;
1372     // TODO: deal with synchronization here: cannot raise any events until after
1373     // this has returned.
1374     return alf.sortBy(aorder, _undoName) ? "true" : "";
1375   }
1376
1377   @Override
1378   public String orderBy(String order, String undoName)
1379   {
1380     return orderBy(order, undoName, null);
1381   }
1382
1383   @Override
1384   public String orderBy(String order, String undoName, String sep)
1385   {
1386     return orderAlignmentBy(Jalview.getCurrentAlignFrame(), order, undoName,
1387             sep);
1388   }
1389
1390   /**
1391    * Allow an outside entity to initiate the second half of argument parsing
1392    * (only).
1393    * 
1394    * @param args
1395    * @return null is good
1396    */
1397   @Override
1398   public Object parseArguments(String[] args)
1399   {
1400
1401     try
1402     {
1403       Jalview.getInstance().parseArguments(new ArgsParser(args), false);
1404       return null;
1405     } catch (Throwable t)
1406     {
1407       return t;
1408     }
1409   }
1410
1411   @Override
1412   public void removeSelectionListener(AlignFrame af, String listener)
1413   {
1414
1415     List<SelectionListener> listeners = Desktop
1416             .getStructureSelectionManager().getListeners();
1417     for (int i = listeners.size(); --i >= 0;)
1418     {
1419       SelectionListener l = listeners.get(i);
1420       if (l instanceof JsSelectionListener
1421               && ((JsSelectionListener) l).isFor(af, listener))
1422       {
1423         listeners.remove(i);
1424         break;
1425       }
1426     }
1427   }
1428
1429   @Override
1430   public void scrollViewToColumnIn(final AlignFrame alf,
1431           final String leftHandColumn)
1432   {
1433     java.awt.EventQueue.invokeLater(new Runnable()
1434     {
1435
1436       @Override
1437       public void run()
1438       {
1439         try
1440         {
1441           (alf == null ? Jalview.getCurrentAlignFrame() : alf)
1442                   .scrollToColumn(
1443                           Integer.valueOf(leftHandColumn).intValue());
1444
1445         } catch (Exception ex)
1446         {
1447           System.err.println(
1448                   "Couldn't parse integer arguments (leftHandColumn='"
1449                           + leftHandColumn + "')");
1450           ex.printStackTrace();
1451         }
1452       }
1453     });
1454
1455   }
1456
1457   @Override
1458   public void scrollViewToIn(final AlignFrame alf, final String topRow,
1459           final String leftHandColumn)
1460   {
1461     // TODO test
1462     java.awt.EventQueue.invokeLater(new Runnable()
1463     {
1464       @Override
1465       public void run()
1466       {
1467         try
1468         {
1469           (alf == null ? Jalview.getCurrentAlignFrame() : alf).scrollTo(
1470                   Integer.valueOf(topRow).intValue(),
1471                   Integer.valueOf(leftHandColumn).intValue());
1472
1473         } catch (Exception ex)
1474         {
1475           System.err.println("Couldn't parse integer arguments (topRow='"
1476                   + topRow + "' and leftHandColumn='" + leftHandColumn
1477                   + "')");
1478           ex.printStackTrace();
1479         }
1480       }
1481     });
1482   }
1483
1484   @Override
1485   public void scrollViewToRowIn(final AlignFrame alf, final String topRow)
1486   {
1487     // TODO test
1488
1489     java.awt.EventQueue.invokeLater(new Runnable()
1490     {
1491       @Override
1492       public void run()
1493       {
1494         try
1495         {
1496           (alf == null ? Jalview.getCurrentAlignFrame() : alf)
1497                   .scrollToRow(Integer.valueOf(topRow).intValue());
1498
1499         } catch (Exception ex)
1500         {
1501           System.err.println("Couldn't parse integer arguments (topRow='"
1502                   + topRow + "')");
1503           ex.printStackTrace();
1504         }
1505
1506       }
1507     });
1508   }
1509
1510   @Override
1511   public void select(String sequenceIds, String columns)
1512   {
1513     selectIn(Jalview.getCurrentAlignFrame(), sequenceIds, columns, null);
1514   }
1515
1516   @Override
1517   public void select(String sequenceIds, String columns, String sep)
1518   {
1519     selectIn(null, sequenceIds, columns, sep);
1520   }
1521
1522   // @Override
1523   // public AlignFrame newView()
1524   // {
1525   // return newViewFrom(null, null);
1526   // }
1527   //
1528   // @Override
1529   // public AlignFrame newView(String name)
1530   // {
1531   // return newViewFrom(null, name);
1532   // }
1533   //
1534   // @Override
1535   // public AlignFrame newViewFrom(AlignFrame alf)
1536   // {
1537   // return newViewFrom(alf, null);
1538   // }
1539   //
1540   // @Override
1541   // public AlignFrame newViewFrom(AlignFrame alf, String name)
1542   // {
1543   // if (alf == null)
1544   // {
1545   // alf = Jalview.getCurrentAlignFrame();
1546   // }
1547   // return appLoader.newViewFrom(alf, name);
1548   // }
1549
1550   @Override
1551   public void selectIn(AlignFrame alf, String sequenceIds, String columns)
1552   {
1553     selectIn(alf, sequenceIds, columns, null);
1554   }
1555
1556   @Override
1557   public void selectIn(AlignFrame af, String sequenceIds, String columns,
1558           String sep)
1559   {
1560     AlignFrame alf = (af == null ? Jalview.getCurrentAlignFrame() : af);
1561
1562     if (sep == null || sep.length() == 0)
1563     {
1564       sep = separator;
1565     }
1566     else
1567     {
1568       if (debug)
1569       {
1570         System.err.println("Selecting region using separator string '"
1571                 + separator + "'");
1572       }
1573     }
1574     // deparse fields
1575     String[] ids = separatorListToArray(sequenceIds, sep);
1576     String[] cols = separatorListToArray(columns, sep);
1577     final SequenceGroup sel = new SequenceGroup();
1578     final ColumnSelection csel = new ColumnSelection();
1579     AlignmentI al = alf.getViewport().getAlignment();
1580     jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
1581             alf.getViewport().getAlignment().getSequencesArray());
1582     int start = 0, end = al.getWidth(), alw = al.getWidth();
1583     boolean seqsfound = true;
1584     if (ids != null && ids.length > 0)
1585     {
1586       seqsfound = false;
1587       for (int i = 0; i < ids.length; i++)
1588       {
1589         if (ids[i].trim().length() == 0)
1590         {
1591           continue;
1592         }
1593         SequenceI sq = matcher.findIdMatch(ids[i]);
1594         if (sq != null)
1595         {
1596           seqsfound = true;
1597           sel.addSequence(sq, false);
1598         }
1599       }
1600     }
1601     boolean inseqpos = false;
1602     if (cols != null && cols.length > 0)
1603     {
1604       boolean seset = false;
1605       for (int i = 0; i < cols.length; i++)
1606       {
1607         String cl = cols[i].trim();
1608         if (cl.length() == 0)
1609         {
1610           continue;
1611         }
1612         int p;
1613         if ((p = cl.indexOf("-")) > -1)
1614         {
1615           int from = -1, to = -1;
1616           try
1617           {
1618             from = Integer.valueOf(cl.substring(0, p)).intValue();
1619             from--;
1620           } catch (NumberFormatException ex)
1621           {
1622             System.err.println(
1623                     "ERROR: Couldn't parse first integer in range element column selection string '"
1624                             + cl + "' - format is 'from-to'");
1625             return;
1626           }
1627           try
1628           {
1629             to = Integer.valueOf(cl.substring(p + 1)).intValue();
1630             to--;
1631           } catch (NumberFormatException ex)
1632           {
1633             System.err.println(
1634                     "ERROR: Couldn't parse second integer in range element column selection string '"
1635                             + cl + "' - format is 'from-to'");
1636             return;
1637           }
1638           if (from >= 0 && to >= 0)
1639           {
1640             // valid range
1641             if (from < to)
1642             {
1643               int t = to;
1644               to = from;
1645               to = t;
1646             }
1647             if (!seset)
1648             {
1649               start = from;
1650               end = to;
1651               seset = true;
1652             }
1653             else
1654             {
1655               // comment to prevent range extension
1656               if (start > from)
1657               {
1658                 start = from;
1659               }
1660               if (end < to)
1661               {
1662                 end = to;
1663               }
1664             }
1665             for (int r = from; r <= to; r++)
1666             {
1667               if (r >= 0 && r < alw)
1668               {
1669                 csel.addElement(r);
1670               }
1671             }
1672             if (debug)
1673             {
1674               System.err.println("Range '" + cl + "' deparsed as [" + from
1675                       + "," + to + "]");
1676             }
1677           }
1678           else
1679           {
1680             System.err.println("ERROR: Invalid Range '" + cl
1681                     + "' deparsed as [" + from + "," + to + "]");
1682           }
1683         }
1684         else
1685         {
1686           int r = -1;
1687           try
1688           {
1689             r = Integer.valueOf(cl).intValue();
1690             r--;
1691           } catch (NumberFormatException ex)
1692           {
1693             if (cl.toLowerCase().equals("sequence"))
1694             {
1695               // we are in the dataset sequence's coordinate frame.
1696               inseqpos = true;
1697             }
1698             else
1699             {
1700               System.err.println(
1701                       "ERROR: Couldn't parse integer from point selection element of column selection string '"
1702                               + cl + "'");
1703               return;
1704             }
1705           }
1706           if (r >= 0 && r <= alw)
1707           {
1708             if (!seset)
1709             {
1710               start = r;
1711               end = r;
1712               seset = true;
1713             }
1714             else
1715             {
1716               // comment to prevent range extension
1717               if (start > r)
1718               {
1719                 start = r;
1720               }
1721               if (end < r)
1722               {
1723                 end = r;
1724               }
1725             }
1726             csel.addElement(r);
1727             if (debug)
1728             {
1729               System.err.println("Point selection '" + cl
1730                       + "' deparsed as [" + r + "]");
1731             }
1732           }
1733           else
1734           {
1735             System.err.println("ERROR: Invalid Point selection '" + cl
1736                     + "' deparsed as [" + r + "]");
1737           }
1738         }
1739       }
1740     }
1741     if (seqsfound)
1742     {
1743       // we only propagate the selection when it was the null selection, or the
1744       // given sequences were found in the alignment.
1745       if (inseqpos && sel.getSize() > 0)
1746       {
1747         // assume first sequence provides reference frame ?
1748         SequenceI rs = sel.getSequenceAt(0);
1749         start = rs.findIndex(start);
1750         end = rs.findIndex(end);
1751         List<Integer> cs = new ArrayList<>(csel.getSelected());
1752         csel.clear();
1753         for (Integer selectedCol : cs)
1754         {
1755           csel.addElement(rs.findIndex(selectedCol));
1756         }
1757       }
1758       sel.setStartRes(start);
1759       sel.setEndRes(end);
1760       EventQueue.invokeLater(new Runnable()
1761       {
1762         @Override
1763         public void run()
1764         {
1765           alf.select(sel, csel,
1766                   alf.getCurrentView().getAlignment().getHiddenColumns());
1767         }
1768       });
1769     }
1770   }
1771
1772   // public AlignFrame newViewFrom(AlignFrame alf, String name)
1773   // {
1774   // return (AlignFrame) alf.newView(name, true);
1775   // }
1776   //
1777   @Override
1778   public String[] separatorListToArray(String list)
1779   {
1780     return separatorListToArray(list, separator);
1781   }
1782
1783   @Override
1784   public void setFeatureGroupState(String[] groups, boolean state)
1785   {
1786     setFeatureGroupState(null, groups, state);
1787   }
1788
1789   @Override
1790   public void setFeatureGroupState(String groups, boolean state)
1791   { // JalviewLite API
1792     setFeatureGroupStateOn(null, groups, state);
1793   }
1794
1795   @Override
1796   public void setFeatureGroupStateOn(final AlignFrame alf,
1797           final String groups, boolean state)
1798   {
1799     setFeatureGroupState(alf, separatorListToArray(groups, separator), state);
1800 //    java.awt.EventQueue.invokeLater(new Runnable()
1801 //    {
1802 //      @Override
1803 //      public void run()
1804 //      {
1805 //        (alf == null ? Jalview.getCurrentAlignFrame() : alf)
1806 //                .setFeatureGroupState(
1807 //                        separatorListToArray(groups, separator), state);
1808 //      }
1809 //    });
1810   }
1811
1812   public void setFeatureGroupState(AlignFrame af, String[] groups, boolean state) {
1813     (af == null ? Jalview.getCurrentAlignFrame() : af).setFeatureGroupState(groups, state);
1814   }
1815
1816
1817   @Override
1818   public void setSelectionListener(AlignFrame af, String listener)
1819   {
1820     Desktop.getStructureSelectionManager()
1821             .addSelectionListener(new JsSelectionListener(af, listener));
1822   }
1823
1824   @Override
1825   public void setSelectionListener(String listener)
1826   {
1827     Desktop.getStructureSelectionManager()
1828             .addSelectionListener(new JsSelectionListener(null, listener));
1829   }
1830
1831   @Override
1832   public void setSeparator(String separator)
1833   {
1834     this.separator = separator;
1835   }
1836
1837   @Override
1838   public void showOverview()
1839   {
1840     Jalview.getCurrentAlignFrame().overviewMenuItem_actionPerformed(null);
1841   }
1842
1843   /**
1844    * Allowing for a JavaScript function here.
1845    */
1846   public void callInitCallback()
1847   {
1848     Object initjscallback = getAppletParameter("oninit", false);
1849     if (initjscallback != null)
1850     {
1851       try
1852       {
1853         doSendCallback(initjscallback, new Object[0]);
1854       } catch (Exception e)
1855       {
1856         System.err.println("Exception when executing _oninit callback '"
1857                 + initjscallback + "'.");
1858         e.printStackTrace();
1859       }
1860     }
1861   }
1862
1863   /**
1864    * Pass the provided array prepended with Jalview.this
1865    * 
1866    * Appropriated from org.jmol.appletjs.Jmol
1867    * 
1868    * @param callback
1869    *          a window function or "alert"
1870    * @param data
1871    * @return String return from the callback method.
1872    */
1873   public String doSendCallback(Object callback, Object[] data)
1874   {
1875     Jalview me = Jalview.getInstance();
1876
1877     if (me != null && callback != null)
1878     {
1879       /**
1880        * @j2sNative
1881        * 
1882        *            try{
1883        * 
1884        *            if (callback == "alert") { alert(data[0]); return ""; } var
1885        *            o; if (typeof callback == "function") { o = callback; } else
1886        *            { if (!callback)return; var tokens = callback.split("."); o
1887        *            = window[tokens[0]]; for (var i = 1; i < tokens.length; i++)
1888        *            o = o[tokens[i]]; } var a = [me]; for (var i = 0; i <
1889        *            data.length; i++) a.push(data[i] ? data[i].booleanValue &&
1890        *            (data[i] = data[i].booleanValue()) : data[i]); return
1891        *            o.apply(null,a) } catch (e) { System.out.println(callback +
1892        *            " failed " + e); }
1893        */
1894     }
1895     return "";
1896   }
1897
1898   private DataSourceType resolveFileProtocol(String[] retPath)
1899   {
1900     String path = retPath[0];
1901     /*
1902      * is it paste data?
1903      */
1904     if (path.startsWith("PASTE"))
1905     {
1906       retPath[0] = path.substring(5);
1907       return DataSourceType.PASTE;
1908     }
1909
1910     /*
1911      * is it a URL?
1912      */
1913     if (path.indexOf("://") >= 0)
1914     {
1915       return DataSourceType.URL;
1916     }
1917
1918     /*
1919      * try relative to document root
1920      */
1921     URL documentBase = getDocumentBase();
1922     String withDocBase = resolveUrlForLocalOrAbsolute(path, documentBase);
1923     if (HttpUtils.isValidUrl(withDocBase))
1924     {
1925       // if (debug)
1926       // {
1927       // System.err.println("Prepended document base '" + documentBase
1928       // + "' to make: '" + withDocBase + "'");
1929       // }
1930       retPath[0] = withDocBase;
1931       return DataSourceType.URL;
1932     }
1933
1934     /*
1935      * try relative to codebase (if different to document base)
1936      */
1937     URL codeBase = getCodeBase();
1938     String withCodeBase = resolveUrlForLocalOrAbsolute(path, codeBase);
1939     if (!withCodeBase.equals(withDocBase)
1940             && HttpUtils.isValidUrl(withCodeBase))
1941     {
1942       // if (debug)
1943       // {
1944       // System.err.println("Prepended codebase '" + codeBase
1945       // + "' to make: '" + withCodeBase + "'");
1946       // }
1947       retPath[0] = withCodeBase;
1948       return DataSourceType.URL;
1949     }
1950
1951     /*
1952      * try locating by classloader; try this last so files in the directory
1953      * are resolved using document base
1954      */
1955     if (inArchive(getClass(), path))
1956     {
1957       return DataSourceType.CLASSLOADER;
1958     }
1959     return null;
1960   }
1961
1962   /**
1963    * Discovers whether the given file is in the Applet Archive
1964    * 
1965    * @param f
1966    *          String
1967    * @return boolean
1968    */
1969   private static boolean inArchive(Class<?> c, String f)
1970   {
1971     // This might throw a security exception in certain browsers
1972     // Netscape Communicator for instance.
1973     try
1974     {
1975       boolean rtn = (c.getResourceAsStream("/" + f) != null);
1976       return rtn;
1977     } catch (Exception ex)
1978     {
1979       System.out.println("Exception checking resources: " + f + " " + ex);
1980       return false;
1981     }
1982   }
1983
1984   /**
1985    * form a complete URL given a path to a resource and a reference location on
1986    * the same server
1987    * 
1988    * @param targetPath
1989    *          - an absolute path on the same server as localref or a document
1990    *          located relative to localref
1991    * @param localref
1992    *          - a URL on the same server as url
1993    * @return a complete URL for the resource located by url
1994    */
1995   public static String resolveUrlForLocalOrAbsolute(String targetPath,
1996           URL localref)
1997   {
1998     String resolvedPath = "";
1999     if (targetPath.startsWith("/"))
2000     {
2001       String codebase = localref.toString();
2002       String localfile = localref.getFile();
2003       resolvedPath = codebase.substring(0,
2004               codebase.length() - localfile.length()) + targetPath;
2005       return resolvedPath;
2006     }
2007
2008     /*
2009      * get URL path and strip off any trailing file e.g.
2010      * www.jalview.org/examples/index.html#applets?a=b is trimmed to
2011      * www.jalview.org/examples/
2012      */
2013     String urlPath = localref.toString();
2014     String directoryPath = urlPath;
2015     int lastSeparator = directoryPath.lastIndexOf("/");
2016     if (lastSeparator > 0)
2017     {
2018       directoryPath = directoryPath.substring(0, lastSeparator + 1);
2019     }
2020
2021     if (targetPath.startsWith("/"))
2022     {
2023       /*
2024        * construct absolute URL to a file on the server - this is not allowed?
2025        */
2026       // String localfile = localref.getFile();
2027       // resolvedPath = urlPath.substring(0,
2028       // urlPath.length() - localfile.length())
2029       // + targetPath;
2030       resolvedPath = directoryPath + targetPath.substring(1);
2031     }
2032     else
2033     {
2034       resolvedPath = directoryPath + targetPath;
2035     }
2036     // if (debug)
2037     // {
2038     // System.err.println(
2039     // "resolveUrlForLocalOrAbsolute returning " + resolvedPath);
2040     // }
2041     return resolvedPath;
2042   }
2043
2044   /**
2045    * parse the string into a list
2046    * 
2047    * @param list
2048    * @param separator
2049    * @return elements separated by separator
2050    */
2051   public static String[] separatorListToArray(String list, String separator)
2052   {
2053     // TODO use StringUtils version (slightly different...)
2054     int seplen = separator.length();
2055     if (list == null || list.equals("") || list.equals(separator))
2056     {
2057       return null;
2058     }
2059     Vector<String> jv = new Vector<>();
2060     int cp = 0, pos;
2061     while ((pos = list.indexOf(separator, cp)) > cp)
2062     {
2063       jv.addElement(list.substring(cp, pos));
2064       cp = pos + seplen;
2065     }
2066     if (cp < list.length())
2067     {
2068       String c = list.substring(cp);
2069       if (!c.equals(separator))
2070       {
2071         jv.addElement(c);
2072       }
2073     }
2074     if (jv.size() > 0)
2075     {
2076       String[] v = new String[jv.size()];
2077       for (int i = 0; i < v.length; i++)
2078       {
2079         v[i] = jv.elementAt(i);
2080       }
2081       jv.removeAllElements();
2082       return v;
2083     }
2084     return null;
2085   }
2086
2087   public class JsSelectionListener
2088           implements jalview.structure.SelectionListener
2089   {
2090
2091     AlignFrame _af;
2092
2093     String _listener;
2094
2095     public JsSelectionListener(AlignFrame af, String listener)
2096     {
2097       _af = af;
2098       _listener = listener;
2099     }
2100
2101     @Override
2102     public void selection(SequenceGroup seqsel, ColumnSelection colsel,
2103             HiddenColumns hidden, SelectionSource source)
2104     {
2105       // System.err.println("Testing selection event relay to
2106       // jsfunction:"+_listener);
2107       String setid = "";
2108       AlignFrame src = _af;
2109       if (source != null)
2110       {
2111         if (source instanceof AlignViewport
2112                 && Jalview.getCurrentAlignFrame().getViewport() == source)
2113         {
2114           src = Jalview.getCurrentAlignFrame();
2115           if (src != _af)
2116             return;
2117         }
2118       }
2119       String[] seqs = new String[] {};
2120       String[] cols = new String[] {};
2121       int strt = 0, end = (src == null) ? -1
2122               : src.alignPanel.av.getAlignment().getWidth();
2123       if (seqsel != null && seqsel.getSize() > 0)
2124       {
2125         seqs = new String[seqsel.getSize()];
2126         for (int i = 0; i < seqs.length; i++)
2127         {
2128           seqs[i] = seqsel.getSequenceAt(i).getName();
2129         }
2130         if (strt < seqsel.getStartRes())
2131         {
2132           strt = seqsel.getStartRes();
2133         }
2134         if (end == -1 || end > seqsel.getEndRes())
2135         {
2136           end = seqsel.getEndRes();
2137         }
2138       }
2139       if (colsel != null && !colsel.isEmpty())
2140       {
2141         if (end == -1)
2142         {
2143           end = colsel.getMax() + 1;
2144         }
2145         cols = new String[colsel.getSelected().size()];
2146         for (int i = 0; i < cols.length; i++)
2147         {
2148           cols[i] = "" + (1 + colsel.getSelected().get(i).intValue());
2149         }
2150       }
2151       else
2152       {
2153         if (seqsel != null && seqsel.getSize() > 0)
2154         {
2155           // send a valid range, otherwise we send the empty selection
2156           cols = new String[2];
2157           cols[0] = "" + (1 + strt) + "-" + (1 + end);
2158         }
2159         ;
2160
2161       }
2162
2163       doSendCallback(_listener,
2164               new Object[]
2165               { src, setid, arrayToSeparatorList(seqs),
2166                   arrayToSeparatorList(cols) });
2167     }
2168
2169     public boolean isFor(AlignFrame af, String listener)
2170     {
2171       return _af == af && _listener.contentEquals(listener);
2172     }
2173
2174   }
2175
2176   @Override
2177   public AlignViewportI getViewport()
2178   {
2179     return Jalview.getCurrentAlignFrame().getViewport();
2180   }
2181
2182 }