Merge branch 'develop' into update_212_Dec_merge_with_21125_chamges
[jalview.git] / src / jalview / bin / JalviewLite.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.bin;
22
23 import java.util.Locale;
24
25 import jalview.analysis.AlignmentUtils;
26 import jalview.api.StructureSelectionManagerProvider;
27 import jalview.appletgui.AlignFrame;
28 import jalview.appletgui.AlignViewport;
29 import jalview.appletgui.EmbmenuFrame;
30 import jalview.appletgui.FeatureSettings;
31 import jalview.appletgui.SplitFrame;
32 import jalview.datamodel.Alignment;
33 import jalview.datamodel.AlignmentI;
34 import jalview.datamodel.AlignmentOrder;
35 import jalview.datamodel.ColumnSelection;
36 import jalview.datamodel.PDBEntry;
37 import jalview.datamodel.Sequence;
38 import jalview.datamodel.SequenceGroup;
39 import jalview.datamodel.SequenceI;
40 import jalview.io.AnnotationFile;
41 import jalview.io.AppletFormatAdapter;
42 import jalview.io.DataSourceType;
43 import jalview.io.FileFormatI;
44 import jalview.io.FileFormats;
45 import jalview.io.FileParse;
46 import jalview.io.IdentifyFile;
47 import jalview.io.JPredFile;
48 import jalview.io.JnetAnnotationMaker;
49 import jalview.io.NewickFile;
50 import jalview.appletgui.js.JSFunctionExec;
51 import jalview.appletgui.js.JalviewLiteJsApi;
52 import jalview.appletgui.js.JsCallBack;
53 import jalview.appletgui.js.JsSelectionSender;
54 import jalview.appletgui.js.MouseOverListener;
55 import jalview.appletgui.js.MouseOverStructureListener;
56 import jalview.structure.SelectionListener;
57 import jalview.structure.StructureSelectionManager;
58 import jalview.util.ColorUtils;
59 import jalview.util.HttpUtils;
60 import jalview.util.MessageManager;
61
62 import java.applet.Applet;
63 import java.awt.Button;
64 import java.awt.Color;
65 import java.awt.Component;
66 import java.awt.EventQueue;
67 import java.awt.Font;
68 import java.awt.Frame;
69 import java.awt.Graphics;
70 import java.awt.event.ActionEvent;
71 import java.awt.event.WindowAdapter;
72 import java.awt.event.WindowEvent;
73 import java.io.BufferedReader;
74 import java.io.IOException;
75 import java.io.InputStreamReader;
76 import java.net.URL;
77 import java.util.ArrayList;
78 import java.util.Hashtable;
79 import java.util.List;
80 import java.util.StringTokenizer;
81 import java.util.Vector;
82
83 import netscape.javascript.JSObject;
84
85 /**
86  * Jalview Applet. Runs in Java 1.18 runtime
87  * 
88  * @author $author$
89  * @version $Revision: 1.92 $
90  */
91 public class JalviewLite extends Applet
92         implements StructureSelectionManagerProvider, JalviewLiteJsApi
93 {
94
95   private static final String TRUE = "true";
96
97   private static final String FALSE = "false";
98
99   public StructureSelectionManager getStructureSelectionManager()
100   {
101     return StructureSelectionManager.getStructureSelectionManager(this);
102   }
103
104   // /////////////////////////////////////////
105   // The following public methods may be called
106   // externally, eg via javascript in HTML page
107   /*
108    * (non-Javadoc)
109    * 
110    * @see jalview.bin.JalviewLiteJsApi#getSelectedSequences()
111    */
112   @Override
113   public String getSelectedSequences()
114   {
115     return getSelectedSequencesFrom(getDefaultTargetFrame());
116   }
117
118   /*
119    * (non-Javadoc)
120    * 
121    * @see jalview.bin.JalviewLiteJsApi#getSelectedSequences(java.lang.String)
122    */
123   @Override
124   public String getSelectedSequences(String sep)
125   {
126     return getSelectedSequencesFrom(getDefaultTargetFrame(), sep);
127   }
128
129   /*
130    * (non-Javadoc)
131    * 
132    * @see
133    * jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
134    * .AlignFrame)
135    */
136   @Override
137   public String getSelectedSequencesFrom(AlignFrame alf)
138   {
139     return getSelectedSequencesFrom(alf, separator); // ""+0x00AC);
140   }
141
142   /*
143    * (non-Javadoc)
144    * 
145    * @see
146    * jalview.bin.JalviewLiteJsApi#getSelectedSequencesFrom(jalview.appletgui
147    * .AlignFrame, java.lang.String)
148    */
149   @Override
150   public String getSelectedSequencesFrom(AlignFrame alf, String sep)
151   {
152     StringBuffer result = new StringBuffer("");
153     if (sep == null || sep.length() == 0)
154     {
155       sep = separator; // "+0x00AC;
156     }
157     if (alf.viewport.getSelectionGroup() != null)
158     {
159       SequenceI[] seqs = alf.viewport.getSelectionGroup()
160               .getSequencesInOrder(alf.viewport.getAlignment());
161
162       for (int i = 0; i < seqs.length; i++)
163       {
164         result.append(seqs[i].getName());
165         result.append(sep);
166       }
167     }
168
169     return result.toString();
170   }
171
172   /*
173    * (non-Javadoc)
174    * 
175    * @see jalview.bin.JalviewLiteJsApi#highlight(java.lang.String,
176    * java.lang.String, java.lang.String)
177    */
178   @Override
179   public void highlight(String sequenceId, String position,
180           String alignedPosition)
181   {
182     highlightIn(getDefaultTargetFrame(), sequenceId, position,
183             alignedPosition);
184   }
185
186   /*
187    * (non-Javadoc)
188    * 
189    * @see jalview.bin.JalviewLiteJsApi#highlightIn(jalview.appletgui.AlignFrame,
190    * java.lang.String, java.lang.String, java.lang.String)
191    */
192   @Override
193   public void highlightIn(final AlignFrame alf, final String sequenceId,
194           final String position, final String alignedPosition)
195   {
196     // TODO: could try to highlight in all alignments if alf==null
197     jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
198             alf.viewport.getAlignment().getSequencesArray());
199     final SequenceI sq = matcher.findIdMatch(sequenceId);
200     if (sq != null)
201     {
202       int apos = -1;
203       try
204       {
205         apos = Integer.valueOf(position).intValue();
206         apos--;
207       } catch (NumberFormatException ex)
208       {
209         return;
210       }
211       final StructureSelectionManagerProvider me = this;
212       final int pos = apos;
213       // use vamsas listener to broadcast to all listeners in scope
214       if (alignedPosition != null
215               && (alignedPosition.trim().length() == 0 || alignedPosition
216                       .toLowerCase(Locale.ROOT).indexOf("false") > -1))
217       {
218         java.awt.EventQueue.invokeLater(new Runnable()
219         {
220           @Override
221           public void run()
222           {
223             StructureSelectionManager.getStructureSelectionManager(me)
224                     .mouseOverVamsasSequence(sq, sq.findIndex(pos), null);
225           }
226         });
227       }
228       else
229       {
230         java.awt.EventQueue.invokeLater(new Runnable()
231         {
232           @Override
233           public void run()
234           {
235             StructureSelectionManager.getStructureSelectionManager(me)
236                     .mouseOverVamsasSequence(sq, pos, null);
237           }
238         });
239       }
240     }
241   }
242
243   /*
244    * (non-Javadoc)
245    * 
246    * @see jalview.bin.JalviewLiteJsApi#select(java.lang.String,
247    * java.lang.String)
248    */
249   @Override
250   public void select(String sequenceIds, String columns)
251   {
252     selectIn(getDefaultTargetFrame(), sequenceIds, columns, separator);
253   }
254
255   /*
256    * (non-Javadoc)
257    * 
258    * @see jalview.bin.JalviewLiteJsApi#select(java.lang.String,
259    * java.lang.String, java.lang.String)
260    */
261   @Override
262   public void select(String sequenceIds, String columns, String sep)
263   {
264     selectIn(getDefaultTargetFrame(), sequenceIds, columns, sep);
265   }
266
267   /*
268    * (non-Javadoc)
269    * 
270    * @see jalview.bin.JalviewLiteJsApi#selectIn(jalview.appletgui.AlignFrame,
271    * java.lang.String, java.lang.String)
272    */
273   @Override
274   public void selectIn(AlignFrame alf, String sequenceIds, String columns)
275   {
276     selectIn(alf, sequenceIds, columns, separator);
277   }
278
279   /*
280    * (non-Javadoc)
281    * 
282    * @see jalview.bin.JalviewLiteJsApi#selectIn(jalview.appletgui.AlignFrame,
283    * java.lang.String, java.lang.String, java.lang.String)
284    */
285   @Override
286   public void selectIn(final AlignFrame alf, String sequenceIds,
287           String columns, String sep)
288   {
289     if (sep == null || sep.length() == 0)
290     {
291       sep = separator;
292     }
293     else
294     {
295       if (debug)
296       {
297         System.err.println("Selecting region using separator string '"
298                 + separator + "'");
299       }
300     }
301     // deparse fields
302     String[] ids = separatorListToArray(sequenceIds, sep);
303     String[] cols = separatorListToArray(columns, sep);
304     final SequenceGroup sel = new SequenceGroup();
305     final ColumnSelection csel = new ColumnSelection();
306     AlignmentI al = alf.viewport.getAlignment();
307     jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
308             alf.viewport.getAlignment().getSequencesArray());
309     int start = 0, end = al.getWidth(), alw = al.getWidth();
310     boolean seqsfound = true;
311     if (ids != null && ids.length > 0)
312     {
313       seqsfound = false;
314       for (int i = 0; i < ids.length; i++)
315       {
316         if (ids[i].trim().length() == 0)
317         {
318           continue;
319         }
320         SequenceI sq = matcher.findIdMatch(ids[i]);
321         if (sq != null)
322         {
323           seqsfound = true;
324           sel.addSequence(sq, false);
325         }
326       }
327     }
328     boolean inseqpos = false;
329     if (cols != null && cols.length > 0)
330     {
331       boolean seset = false;
332       for (int i = 0; i < cols.length; i++)
333       {
334         String cl = cols[i].trim();
335         if (cl.length() == 0)
336         {
337           continue;
338         }
339         int p;
340         if ((p = cl.indexOf("-")) > -1)
341         {
342           int from = -1, to = -1;
343           try
344           {
345             from = Integer.valueOf(cl.substring(0, p)).intValue();
346             from--;
347           } catch (NumberFormatException ex)
348           {
349             System.err.println(
350                     "ERROR: Couldn't parse first integer in range element column selection string '"
351                             + cl + "' - format is 'from-to'");
352             return;
353           }
354           try
355           {
356             to = Integer.valueOf(cl.substring(p + 1)).intValue();
357             to--;
358           } catch (NumberFormatException ex)
359           {
360             System.err.println(
361                     "ERROR: Couldn't parse second integer in range element column selection string '"
362                             + cl + "' - format is 'from-to'");
363             return;
364           }
365           if (from >= 0 && to >= 0)
366           {
367             // valid range
368             if (from < to)
369             {
370               int t = to;
371               to = from;
372               to = t;
373             }
374             if (!seset)
375             {
376               start = from;
377               end = to;
378               seset = true;
379             }
380             else
381             {
382               // comment to prevent range extension
383               if (start > from)
384               {
385                 start = from;
386               }
387               if (end < to)
388               {
389                 end = to;
390               }
391             }
392             for (int r = from; r <= to; r++)
393             {
394               if (r >= 0 && r < alw)
395               {
396                 csel.addElement(r);
397               }
398             }
399             if (debug)
400             {
401               System.err.println("Range '" + cl + "' deparsed as [" + from
402                       + "," + to + "]");
403             }
404           }
405           else
406           {
407             System.err.println("ERROR: Invalid Range '" + cl
408                     + "' deparsed as [" + from + "," + to + "]");
409           }
410         }
411         else
412         {
413           int r = -1;
414           try
415           {
416             r = Integer.valueOf(cl).intValue();
417             r--;
418           } catch (NumberFormatException ex)
419           {
420             if (cl.toLowerCase(Locale.ROOT).equals("sequence"))
421             {
422               // we are in the dataset sequence's coordinate frame.
423               inseqpos = true;
424             }
425             else
426             {
427               System.err.println(
428                       "ERROR: Couldn't parse integer from point selection element of column selection string '"
429                               + cl + "'");
430               return;
431             }
432           }
433           if (r >= 0 && r <= alw)
434           {
435             if (!seset)
436             {
437               start = r;
438               end = r;
439               seset = true;
440             }
441             else
442             {
443               // comment to prevent range extension
444               if (start > r)
445               {
446                 start = r;
447               }
448               if (end < r)
449               {
450                 end = r;
451               }
452             }
453             csel.addElement(r);
454             if (debug)
455             {
456               System.err.println("Point selection '" + cl
457                       + "' deparsed as [" + r + "]");
458             }
459           }
460           else
461           {
462             System.err.println("ERROR: Invalid Point selection '" + cl
463                     + "' deparsed as [" + r + "]");
464           }
465         }
466       }
467     }
468     if (seqsfound)
469     {
470       // we only propagate the selection when it was the null selection, or the
471       // given sequences were found in the alignment.
472       if (inseqpos && sel.getSize() > 0)
473       {
474         // assume first sequence provides reference frame ?
475         SequenceI rs = sel.getSequenceAt(0);
476         start = rs.findIndex(start);
477         end = rs.findIndex(end);
478         List<Integer> cs = new ArrayList<>(csel.getSelected());
479         csel.clear();
480         for (Integer selectedCol : cs)
481         {
482           csel.addElement(rs.findIndex(selectedCol));
483         }
484       }
485       sel.setStartRes(start);
486       sel.setEndRes(end);
487       EventQueue.invokeLater(new Runnable()
488       {
489         @Override
490         public void run()
491         {
492           alf.select(sel, csel,
493                   alf.getAlignViewport().getAlignment().getHiddenColumns());
494         }
495       });
496     }
497   }
498
499   /*
500    * (non-Javadoc)
501    * 
502    * @see
503    * jalview.bin.JalviewLiteJsApi#getSelectedSequencesAsAlignment(java.lang.
504    * String, java.lang.String)
505    */
506   @Override
507   public String getSelectedSequencesAsAlignment(String format,
508           String suffix)
509   {
510     return getSelectedSequencesAsAlignmentFrom(getDefaultTargetFrame(),
511             format, suffix);
512   }
513
514   /*
515    * (non-Javadoc)
516    * 
517    * @see
518    * jalview.bin.JalviewLiteJsApi#getSelectedSequencesAsAlignmentFrom(jalview
519    * .appletgui.AlignFrame, java.lang.String, java.lang.String)
520    */
521   @Override
522   public String getSelectedSequencesAsAlignmentFrom(AlignFrame alf,
523           String format, String suffix)
524   {
525     try
526     {
527       FileFormatI theFormat = FileFormats.getInstance().forName(format);
528       boolean seqlimits = suffix.equalsIgnoreCase(TRUE);
529       if (alf.viewport.getSelectionGroup() != null)
530       {
531         // JBPNote: getSelectionAsNewSequence behaviour has changed - this
532         // method now returns a full copy of sequence data
533         // TODO consider using getSequenceSelection instead here
534         String reply = new AppletFormatAdapter().formatSequences(theFormat,
535                 new Alignment(alf.viewport.getSelectionAsNewSequence()),
536                 seqlimits);
537         return reply;
538       }
539     } catch (IllegalArgumentException ex)
540     {
541       ex.printStackTrace();
542       return "Error retrieving alignment, possibly invalid format specifier: "
543               + format;
544     }
545     return "";
546   }
547
548   /*
549    * (non-Javadoc)
550    * 
551    * @see jalview.bin.JalviewLiteJsApi#getAlignmentOrder()
552    */
553   @Override
554   public String getAlignmentOrder()
555   {
556     return getAlignmentOrderFrom(getDefaultTargetFrame());
557   }
558
559   /*
560    * (non-Javadoc)
561    * 
562    * @see
563    * jalview.bin.JalviewLiteJsApi#getAlignmentOrderFrom(jalview.appletgui.AlignFrame
564    * )
565    */
566   @Override
567   public String getAlignmentOrderFrom(AlignFrame alf)
568   {
569     return getAlignmentOrderFrom(alf, separator);
570   }
571
572   /*
573    * (non-Javadoc)
574    * 
575    * @see
576    * jalview.bin.JalviewLiteJsApi#getAlignmentOrderFrom(jalview.appletgui.AlignFrame
577    * , java.lang.String)
578    */
579   @Override
580   public String getAlignmentOrderFrom(AlignFrame alf, String sep)
581   {
582     AlignmentI alorder = alf.getAlignViewport().getAlignment();
583     String[] order = new String[alorder.getHeight()];
584     for (int i = 0; i < order.length; i++)
585     {
586       order[i] = alorder.getSequenceAt(i).getName();
587     }
588     return arrayToSeparatorList(order);
589   }
590
591   /*
592    * (non-Javadoc)
593    * 
594    * @see jalview.bin.JalviewLiteJsApi#orderBy(java.lang.String,
595    * java.lang.String)
596    */
597   @Override
598   public String orderBy(String order, String undoName)
599   {
600     return orderBy(order, undoName, separator);
601   }
602
603   /*
604    * (non-Javadoc)
605    * 
606    * @see jalview.bin.JalviewLiteJsApi#orderBy(java.lang.String,
607    * java.lang.String, java.lang.String)
608    */
609   @Override
610   public String orderBy(String order, String undoName, String sep)
611   {
612     return orderAlignmentBy(getDefaultTargetFrame(), order, undoName, sep);
613   }
614
615   /*
616    * (non-Javadoc)
617    * 
618    * @see
619    * jalview.bin.JalviewLiteJsApi#orderAlignmentBy(jalview.appletgui.AlignFrame,
620    * java.lang.String, java.lang.String, java.lang.String)
621    */
622   @Override
623   public String orderAlignmentBy(AlignFrame alf, String order,
624           String undoName, String sep)
625   {
626     String[] ids = separatorListToArray(order, sep);
627     SequenceI[] sqs = null;
628     if (ids != null && ids.length > 0)
629     {
630       jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
631               alf.viewport.getAlignment().getSequencesArray());
632       int s = 0;
633       sqs = new SequenceI[ids.length];
634       for (int i = 0; i < ids.length; i++)
635       {
636         if (ids[i].trim().length() == 0)
637         {
638           continue;
639         }
640         SequenceI sq = matcher.findIdMatch(ids[i]);
641         if (sq != null)
642         {
643           sqs[s++] = sq;
644         }
645       }
646       if (s > 0)
647       {
648         SequenceI[] sqq = new SequenceI[s];
649         System.arraycopy(sqs, 0, sqq, 0, s);
650         sqs = sqq;
651       }
652       else
653       {
654         sqs = null;
655       }
656     }
657     if (sqs == null)
658     {
659       return "";
660     }
661     ;
662     final AlignmentOrder aorder = new AlignmentOrder(sqs);
663
664     if (undoName != null && undoName.trim().length() == 0)
665     {
666       undoName = null;
667     }
668     final String _undoName = undoName;
669     // TODO: deal with synchronization here: cannot raise any events until after
670     // this has returned.
671     return alf.sortBy(aorder, _undoName) ? TRUE : "";
672   }
673
674   /*
675    * (non-Javadoc)
676    * 
677    * @see jalview.bin.JalviewLiteJsApi#getAlignment(java.lang.String)
678    */
679   @Override
680   public String getAlignment(String format)
681   {
682     return getAlignmentFrom(getDefaultTargetFrame(), format, TRUE);
683   }
684
685   /*
686    * (non-Javadoc)
687    * 
688    * @see
689    * jalview.bin.JalviewLiteJsApi#getAlignmentFrom(jalview.appletgui.AlignFrame,
690    * java.lang.String)
691    */
692   @Override
693   public String getAlignmentFrom(AlignFrame alf, String format)
694   {
695     return getAlignmentFrom(alf, format, TRUE);
696   }
697
698   /*
699    * (non-Javadoc)
700    * 
701    * @see jalview.bin.JalviewLiteJsApi#getAlignment(java.lang.String,
702    * java.lang.String)
703    */
704   @Override
705   public String getAlignment(String format, String suffix)
706   {
707     return getAlignmentFrom(getDefaultTargetFrame(), format, suffix);
708   }
709
710   /*
711    * (non-Javadoc)
712    * 
713    * @see
714    * jalview.bin.JalviewLiteJsApi#getAlignmentFrom(jalview.appletgui.AlignFrame,
715    * java.lang.String, java.lang.String)
716    */
717   @Override
718   public String getAlignmentFrom(AlignFrame alf, String format,
719           String suffix)
720   {
721     try
722     {
723       boolean seqlimits = suffix.equalsIgnoreCase(TRUE);
724
725       FileFormatI theFormat = FileFormats.getInstance().forName(format);
726       String reply = new AppletFormatAdapter().formatSequences(theFormat,
727               alf.viewport.getAlignment(), seqlimits);
728       return reply;
729     } catch (IllegalArgumentException ex)
730     {
731       ex.printStackTrace();
732       return "Error retrieving alignment, possibly invalid format specifier: "
733               + format;
734     }
735   }
736
737   /*
738    * (non-Javadoc)
739    * 
740    * @see jalview.bin.JalviewLiteJsApi#loadAnnotation(java.lang.String)
741    */
742   @Override
743   public void loadAnnotation(String annotation)
744   {
745     loadAnnotationFrom(getDefaultTargetFrame(), annotation);
746   }
747
748   /*
749    * (non-Javadoc)
750    * 
751    * @see
752    * jalview.bin.JalviewLiteJsApi#loadAnnotationFrom(jalview.appletgui.AlignFrame
753    * , java.lang.String)
754    */
755   @Override
756   public void loadAnnotationFrom(AlignFrame alf, String annotation)
757   {
758     if (new AnnotationFile().annotateAlignmentView(alf.getAlignViewport(),
759             annotation, DataSourceType.PASTE))
760     {
761       alf.alignPanel.fontChanged();
762       alf.alignPanel.setScrollValues(0, 0);
763     }
764     else
765     {
766       alf.parseFeaturesFile(annotation, DataSourceType.PASTE);
767     }
768   }
769
770   /*
771    * (non-Javadoc)
772    * 
773    * @see jalview.bin.JalviewLiteJsApi#loadAnnotation(java.lang.String)
774    */
775   @Override
776   public void loadFeatures(String features, boolean autoenabledisplay)
777   {
778     loadFeaturesFrom(getDefaultTargetFrame(), features, autoenabledisplay);
779   }
780
781   /*
782    * (non-Javadoc)
783    * 
784    * @see
785    * jalview.bin.JalviewLiteJsApi#loadAnnotationFrom(jalview.appletgui.AlignFrame
786    * , java.lang.String)
787    */
788   @Override
789   public boolean loadFeaturesFrom(AlignFrame alf, String features,
790           boolean autoenabledisplay)
791   {
792     return alf.parseFeaturesFile(features, DataSourceType.PASTE,
793             autoenabledisplay);
794   }
795
796   /*
797    * (non-Javadoc)
798    * 
799    * @see jalview.bin.JalviewLiteJsApi#getFeatures(java.lang.String)
800    */
801   @Override
802   public String getFeatures(String format)
803   {
804     return getFeaturesFrom(getDefaultTargetFrame(), format);
805   }
806
807   /*
808    * (non-Javadoc)
809    * 
810    * @see
811    * jalview.bin.JalviewLiteJsApi#getFeaturesFrom(jalview.appletgui.AlignFrame,
812    * java.lang.String)
813    */
814   @Override
815   public String getFeaturesFrom(AlignFrame alf, String format)
816   {
817     return alf.outputFeatures(false, format);
818   }
819
820   /*
821    * (non-Javadoc)
822    * 
823    * @see jalview.bin.JalviewLiteJsApi#getAnnotation()
824    */
825   @Override
826   public String getAnnotation()
827   {
828     return getAnnotationFrom(getDefaultTargetFrame());
829   }
830
831   /*
832    * (non-Javadoc)
833    * 
834    * @see
835    * jalview.bin.JalviewLiteJsApi#getAnnotationFrom(jalview.appletgui.AlignFrame
836    * )
837    */
838   @Override
839   public String getAnnotationFrom(AlignFrame alf)
840   {
841     return alf.outputAnnotations(false);
842   }
843
844   /*
845    * (non-Javadoc)
846    * 
847    * @see jalview.bin.JalviewLiteJsApi#newView()
848    */
849   @Override
850   public AlignFrame newView()
851   {
852     return newViewFrom(getDefaultTargetFrame());
853   }
854
855   /*
856    * (non-Javadoc)
857    * 
858    * @see jalview.bin.JalviewLiteJsApi#newView(java.lang.String)
859    */
860   @Override
861   public AlignFrame newView(String name)
862   {
863     return newViewFrom(getDefaultTargetFrame(), name);
864   }
865
866   /*
867    * (non-Javadoc)
868    * 
869    * @see jalview.bin.JalviewLiteJsApi#newViewFrom(jalview.appletgui.AlignFrame)
870    */
871   @Override
872   public AlignFrame newViewFrom(AlignFrame alf)
873   {
874     return alf.newView(null);
875   }
876
877   /*
878    * (non-Javadoc)
879    * 
880    * @see jalview.bin.JalviewLiteJsApi#newViewFrom(jalview.appletgui.AlignFrame,
881    * java.lang.String)
882    */
883   @Override
884   public AlignFrame newViewFrom(AlignFrame alf, String name)
885   {
886     return alf.newView(name);
887   }
888
889   /*
890    * (non-Javadoc)
891    * 
892    * @see jalview.bin.JalviewLiteJsApi#loadAlignment(java.lang.String,
893    * java.lang.String)
894    */
895   @Override
896   public AlignFrame loadAlignment(String text, String title)
897   {
898     AlignmentI al = null;
899
900     try
901     {
902       FileFormatI format = new IdentifyFile().identify(text,
903               DataSourceType.PASTE);
904       al = new AppletFormatAdapter().readFile(text, DataSourceType.PASTE,
905               format);
906       if (al.getHeight() > 0)
907       {
908         return new AlignFrame(al, this, title, false);
909       }
910     } catch (IOException ex)
911     {
912       ex.printStackTrace();
913     }
914     return null;
915   }
916
917   /*
918    * (non-Javadoc)
919    * 
920    * @see jalview.bin.JalviewLiteJsApi#setMouseoverListener(java.lang.String)
921    */
922   @Override
923   public void setMouseoverListener(String listener)
924   {
925     setMouseoverListener(currentAlignFrame, listener);
926   }
927
928   private Vector<JSFunctionExec> javascriptListeners = new Vector<>();
929
930   /*
931    * (non-Javadoc)
932    * 
933    * @see
934    * jalview.bin.JalviewLiteJsApi#setMouseoverListener(jalview.appletgui.AlignFrame
935    * , java.lang.String)
936    */
937   @Override
938   public void setMouseoverListener(AlignFrame af, String listener)
939   {
940     if (listener != null)
941     {
942       listener = listener.trim();
943       if (listener.length() == 0)
944       {
945         System.err.println(
946                 "jalview Javascript error: Ignoring empty function for mouseover listener.");
947         return;
948       }
949     }
950     MouseOverListener mol = new MouseOverListener(
951             this, af, listener);
952     javascriptListeners.addElement(mol);
953     StructureSelectionManager.getStructureSelectionManager(this)
954             .addStructureViewerListener(mol);
955     if (debug)
956     {
957       System.err.println("Added a mouseover listener for "
958               + ((af == null) ? "All frames"
959                       : "Just views for "
960                               + af.getAlignViewport().getSequenceSetId()));
961       System.err.println("There are now " + javascriptListeners.size()
962               + " listeners in total.");
963     }
964   }
965
966   /*
967    * (non-Javadoc)
968    * 
969    * @see jalview.bin.JalviewLiteJsApi#setSelectionListener(java.lang.String)
970    */
971   @Override
972   public void setSelectionListener(String listener)
973   {
974     setSelectionListener(null, listener);
975   }
976
977   /*
978    * (non-Javadoc)
979    * 
980    * @see
981    * jalview.bin.JalviewLiteJsApi#setSelectionListener(jalview.appletgui.AlignFrame
982    * , java.lang.String)
983    */
984   @Override
985   public void setSelectionListener(AlignFrame af, String listener)
986   {
987     if (listener != null)
988     {
989       listener = listener.trim();
990       if (listener.length() == 0)
991       {
992         System.err.println(
993                 "jalview Javascript error: Ignoring empty function for selection listener.");
994         return;
995       }
996     }
997     JsSelectionSender mol = new JsSelectionSender(
998             this, af, listener);
999     javascriptListeners.addElement(mol);
1000     StructureSelectionManager.getStructureSelectionManager(this)
1001             .addSelectionListener(mol);
1002     if (debug)
1003     {
1004       System.err.println("Added a selection listener for "
1005               + ((af == null) ? "All frames"
1006                       : "Just views for "
1007                               + af.getAlignViewport().getSequenceSetId()));
1008       System.err.println("There are now " + javascriptListeners.size()
1009               + " listeners in total.");
1010     }
1011   }
1012
1013   /**
1014    * Callable from javascript to register a javascript function to pass events
1015    * to a structure viewer.
1016    *
1017    * @param listener
1018    *          the name of a javascript function
1019    * @param modelSet
1020    *          a token separated list of PDB file names listened for
1021    * @see jalview.bin.JalviewLiteJsApi#setStructureListener(java.lang.String,
1022    *      java.lang.String)
1023    */
1024   @Override
1025   public void setStructureListener(String listener, String modelSet)
1026   {
1027     if (listener != null)
1028     {
1029       listener = listener.trim();
1030       if (listener.length() == 0)
1031       {
1032         System.err.println(
1033                 "jalview Javascript error: Ignoring empty function for selection listener.");
1034         return;
1035       }
1036     }
1037     MouseOverStructureListener mol = new MouseOverStructureListener(this,
1038             listener, separatorListToArray(modelSet));
1039     javascriptListeners.addElement(mol);
1040     StructureSelectionManager.getStructureSelectionManager(this)
1041             .addStructureViewerListener(mol);
1042     if (debug)
1043     {
1044       System.err.println("Added a javascript structure viewer listener '"
1045               + listener + "'");
1046       System.err.println("There are now " + javascriptListeners.size()
1047               + " listeners in total.");
1048     }
1049   }
1050
1051   /*
1052    * (non-Javadoc)
1053    * 
1054    * @see
1055    * jalview.bin.JalviewLiteJsApi#removeJavascriptListener(jalview.appletgui
1056    * .AlignFrame, java.lang.String)
1057    */
1058   @Override
1059   public void removeJavascriptListener(AlignFrame af, String listener)
1060   {
1061     if (listener != null)
1062     {
1063       listener = listener.trim();
1064       if (listener.length() == 0)
1065       {
1066         listener = null;
1067       }
1068     }
1069     boolean rprt = false;
1070     for (int ms = 0, msSize = javascriptListeners.size(); ms < msSize;)
1071     {
1072       Object lstn = javascriptListeners.elementAt(ms);
1073       JsCallBack lstner = (JsCallBack) lstn;
1074       if ((af == null || lstner.getAlignFrame() == af) && (listener == null
1075               || lstner.getListenerFunction().equals(listener)))
1076       {
1077         javascriptListeners.removeElement(lstner);
1078         msSize--;
1079         if (lstner instanceof SelectionListener)
1080         {
1081           StructureSelectionManager.getStructureSelectionManager(this)
1082                   .removeSelectionListener((SelectionListener) lstner);
1083         }
1084         else
1085         {
1086           StructureSelectionManager.getStructureSelectionManager(this)
1087                   .removeStructureViewerListener(lstner, null);
1088         }
1089         rprt = debug;
1090         if (debug)
1091         {
1092           System.err.println("Removed listener '" + listener + "'");
1093         }
1094       }
1095       else
1096       {
1097         ms++;
1098       }
1099     }
1100     if (rprt)
1101     {
1102       System.err.println("There are now " + javascriptListeners.size()
1103               + " listeners in total.");
1104     }
1105   }
1106
1107   @Override
1108   public void stop()
1109   {
1110     System.err.println("Applet " + getName() + " stop().");
1111     tidyUp();
1112   }
1113
1114   @Override
1115   public void destroy()
1116   {
1117     System.err.println("Applet " + getName() + " destroy().");
1118     tidyUp();
1119   }
1120
1121   private void tidyUp()
1122   {
1123     removeAll();
1124     if (currentAlignFrame != null && currentAlignFrame.viewport != null
1125             && currentAlignFrame.viewport.applet != null)
1126     {
1127       AlignViewport av = currentAlignFrame.viewport;
1128       currentAlignFrame.closeMenuItem_actionPerformed();
1129       av.applet = null;
1130       currentAlignFrame = null;
1131     }
1132     if (javascriptListeners != null)
1133     {
1134       while (javascriptListeners.size() > 0)
1135       {
1136         JSFunctionExec mol = javascriptListeners
1137                 .elementAt(0);
1138         javascriptListeners.removeElement(mol);
1139         if (mol instanceof SelectionListener)
1140         {
1141           StructureSelectionManager.getStructureSelectionManager(this)
1142                   .removeSelectionListener((SelectionListener) mol);
1143         }
1144         else
1145         {
1146           StructureSelectionManager.getStructureSelectionManager(this)
1147                   .removeStructureViewerListener(mol, null);
1148         }
1149         mol.jvlite = null;
1150       }
1151     }
1152     if (jsFunctionExec != null)
1153     {
1154       jsFunctionExec.stopQueue();
1155       jsFunctionExec.jvlite = null;
1156     }
1157     initialAlignFrame = null;
1158     jsFunctionExec = null;
1159     javascriptListeners = null;
1160     StructureSelectionManager.release(this);
1161   }
1162
1163   private JSFunctionExec jsFunctionExec;
1164
1165   /*
1166    * (non-Javadoc)
1167    * 
1168    * @see jalview.bin.JalviewLiteJsApi#mouseOverStructure(java.lang.String,
1169    * java.lang.String, java.lang.String)
1170    */
1171   @Override
1172   public void mouseOverStructure(final String pdbResNum, final String chain,
1173           final String pdbfile)
1174   {
1175     final StructureSelectionManagerProvider me = this;
1176     java.awt.EventQueue.invokeLater(new Runnable()
1177     {
1178       @Override
1179       public void run()
1180       {
1181         try
1182         {
1183           StructureSelectionManager.getStructureSelectionManager(me)
1184                   .mouseOverStructure(Integer.valueOf(pdbResNum).intValue(),
1185                           chain, pdbfile);
1186           if (debug)
1187           {
1188             System.err
1189                     .println("mouseOver for '" + pdbResNum + "' in chain '"
1190                             + chain + "' in structure '" + pdbfile + "'");
1191           }
1192         } catch (NumberFormatException e)
1193         {
1194           System.err.println("Ignoring invalid residue number string '"
1195                   + pdbResNum + "'");
1196         }
1197
1198       }
1199     });
1200   }
1201
1202   /*
1203    * (non-Javadoc)
1204    * 
1205    * @see
1206    * jalview.bin.JalviewLiteJsApi#scrollViewToIn(jalview.appletgui.AlignFrame,
1207    * java.lang.String, java.lang.String)
1208    */
1209   @Override
1210   public void scrollViewToIn(final AlignFrame alf, final String topRow,
1211           final String leftHandColumn)
1212   {
1213     java.awt.EventQueue.invokeLater(new Runnable()
1214     {
1215       @Override
1216       public void run()
1217       {
1218         try
1219         {
1220           alf.scrollTo(Integer.valueOf(topRow).intValue(),
1221                   Integer.valueOf(leftHandColumn).intValue());
1222
1223         } catch (Exception ex)
1224         {
1225           System.err.println("Couldn't parse integer arguments (topRow='"
1226                   + topRow + "' and leftHandColumn='" + leftHandColumn
1227                   + "')");
1228           ex.printStackTrace();
1229         }
1230       }
1231     });
1232   }
1233
1234   /*
1235    * (non-Javadoc)
1236    * 
1237    * @see
1238    * JalviewLiteJsApi#scrollViewToRowIn(jalview.appletgui
1239    * .AlignFrame, java.lang.String)
1240    */
1241   @Override
1242   public void scrollViewToRowIn(final AlignFrame alf, final String topRow)
1243   {
1244
1245     java.awt.EventQueue.invokeLater(new Runnable()
1246     {
1247       @Override
1248       public void run()
1249       {
1250         try
1251         {
1252           alf.scrollToRow(Integer.valueOf(topRow).intValue());
1253
1254         } catch (Exception ex)
1255         {
1256           System.err.println("Couldn't parse integer arguments (topRow='"
1257                   + topRow + "')");
1258           ex.printStackTrace();
1259         }
1260
1261       }
1262     });
1263   }
1264
1265   /*
1266    * (non-Javadoc)
1267    * 
1268    * @see
1269    * JalviewLiteJsApi#scrollViewToColumnIn(jalview.appletgui
1270    * .AlignFrame, java.lang.String)
1271    */
1272   @Override
1273   public void scrollViewToColumnIn(final AlignFrame alf,
1274           final String leftHandColumn)
1275   {
1276     java.awt.EventQueue.invokeLater(new Runnable()
1277     {
1278
1279       @Override
1280       public void run()
1281       {
1282         try
1283         {
1284           alf.scrollToColumn(Integer.valueOf(leftHandColumn).intValue());
1285
1286         } catch (Exception ex)
1287         {
1288           System.err.println(
1289                   "Couldn't parse integer arguments (leftHandColumn='"
1290                           + leftHandColumn + "')");
1291           ex.printStackTrace();
1292         }
1293       }
1294     });
1295
1296   }
1297
1298   // //////////////////////////////////////////////
1299   // //////////////////////////////////////////////
1300
1301   public static int lastFrameX = 200;
1302
1303   public static int lastFrameY = 200;
1304
1305   boolean fileFound = true;
1306
1307   String file = "No file";
1308
1309   String file2 = null;
1310
1311   Button launcher = new Button(
1312           MessageManager.getString("label.start_jalview"));
1313
1314   /**
1315    * The currentAlignFrame is static, it will change if and when the user
1316    * selects a new window. Note that it will *never* point back to the embedded
1317    * AlignFrame if the applet is started as embedded on the page and then
1318    * afterwards a new view is created.
1319    */
1320   public AlignFrame currentAlignFrame = null;
1321
1322   /**
1323    * This is the first frame to be displayed, and does not change. API calls
1324    * will default to this instance if currentAlignFrame is null.
1325    */
1326   AlignFrame initialAlignFrame = null;
1327
1328   boolean embedded = false;
1329
1330   private boolean checkForJmol = true;
1331
1332   private boolean checkedForJmol = false; // ensure we don't check for jmol
1333
1334   // every time the app is re-inited
1335
1336   public boolean jmolAvailable = false;
1337
1338   private boolean alignPdbStructures = false;
1339
1340   /**
1341    * use an external structure viewer exclusively (no jmols or mc_views will be
1342    * opened by JalviewLite itself)
1343    */
1344   public boolean useXtrnalSviewer = false;
1345
1346   public static boolean debug = false;
1347
1348   static String builddate = null, version = null, installation = null;
1349
1350   private static void initBuildDetails()
1351   {
1352     if (builddate == null)
1353     {
1354       builddate = "unknown";
1355       version = "test";
1356       installation = "applet";
1357       java.net.URL url = JalviewLite.class
1358               .getResource("/.build_properties");
1359       if (url != null)
1360       {
1361         try
1362         {
1363           BufferedReader reader = new BufferedReader(
1364                   new InputStreamReader(url.openStream()));
1365           String line;
1366           while ((line = reader.readLine()) != null)
1367           {
1368             if (line.indexOf("VERSION") > -1)
1369             {
1370               version = line.substring(line.indexOf("=") + 1);
1371             }
1372             if (line.indexOf("BUILD_DATE") > -1)
1373             {
1374               builddate = line.substring(line.indexOf("=") + 1);
1375             }
1376             if (line.indexOf("INSTALLATION") > -1)
1377             {
1378               installation = line.substring(line.indexOf("=") + 1);
1379             }
1380           }
1381         } catch (Exception ex)
1382         {
1383           ex.printStackTrace();
1384         }
1385       }
1386     }
1387   }
1388
1389   public static String getBuildDate()
1390   {
1391     initBuildDetails();
1392     return builddate;
1393   }
1394
1395   public static String getInstallation()
1396   {
1397     initBuildDetails();
1398     return installation;
1399   }
1400
1401   public static String getVersion()
1402   {
1403     initBuildDetails();
1404     return version;
1405   }
1406
1407   // public JSObject scriptObject = null;
1408
1409   /**
1410    * init method for Jalview Applet
1411    */
1412   @Override
1413   public void init()
1414   {
1415     debug = TRUE.equalsIgnoreCase(getParameter("debug"));
1416     try
1417     {
1418       if (debug)
1419       {
1420         System.err.println("Applet context is '"
1421                 + getAppletContext().getClass().toString() + "'");
1422       }
1423       JSObject scriptObject = JSObject.getWindow(this);
1424       if (debug && scriptObject != null)
1425       {
1426         System.err.println("Applet has Javascript callback support.");
1427       }
1428
1429     } catch (Exception ex)
1430     {
1431       System.err.println(
1432               "Warning: No JalviewLite javascript callbacks available.");
1433       if (debug)
1434       {
1435         ex.printStackTrace();
1436       }
1437     }
1438
1439     if (debug)
1440     {
1441       System.err.println("JalviewLite Version " + getVersion());
1442       System.err.println("Build Date : " + getBuildDate());
1443       System.err.println("Installation : " + getInstallation());
1444     }
1445     String externalsviewer = getParameter("externalstructureviewer");
1446     if (externalsviewer != null)
1447     {
1448       useXtrnalSviewer = externalsviewer.trim().toLowerCase(Locale.ROOT)
1449               .equals(TRUE);
1450     }
1451     /**
1452      * if true disable the check for jmol
1453      */
1454     String chkforJmol = getParameter("nojmol");
1455     if (chkforJmol != null)
1456     {
1457       checkForJmol = !chkforJmol.equals(TRUE);
1458     }
1459     /**
1460      * get the separator parameter if present
1461      */
1462     String sep = getParameter("separator");
1463     if (sep != null)
1464     {
1465       if (sep.length() > 0)
1466       {
1467         separator = sep;
1468         if (debug)
1469         {
1470           System.err.println("Separator set to '" + separator + "'");
1471         }
1472       }
1473       else
1474       {
1475         throw new Error(MessageManager
1476                 .getString("error.invalid_separator_parameter"));
1477       }
1478     }
1479     int r = 255;
1480     int g = 255;
1481     int b = 255;
1482     String param = getParameter("RGB");
1483
1484     if (param != null)
1485     {
1486       try
1487       {
1488         r = Integer.parseInt(param.substring(0, 2), 16);
1489         g = Integer.parseInt(param.substring(2, 4), 16);
1490         b = Integer.parseInt(param.substring(4, 6), 16);
1491       } catch (Exception ex)
1492       {
1493         r = 255;
1494         g = 255;
1495         b = 255;
1496       }
1497     }
1498     param = getParameter("label");
1499     if (param != null)
1500     {
1501       launcher.setLabel(param);
1502     }
1503
1504     setBackground(new Color(r, g, b));
1505
1506     file = getParameter("file");
1507
1508     if (file == null)
1509     {
1510       // Maybe the sequences are added as parameters
1511       StringBuffer data = new StringBuffer("PASTE");
1512       int i = 1;
1513       while ((file = getParameter("sequence" + i)) != null)
1514       {
1515         data.append(file.toString() + "\n");
1516         i++;
1517       }
1518       if (data.length() > 5)
1519       {
1520         file = data.toString();
1521       }
1522     }
1523     if (getDefaultParameter("enableSplitFrame", true))
1524     {
1525       file2 = getParameter("file2");
1526     }
1527
1528     embedded = TRUE.equalsIgnoreCase(getParameter("embedded"));
1529     if (embedded)
1530     {
1531       LoadingThread loader = new LoadingThread(file, file2, this);
1532       loader.start();
1533     }
1534     else if (file != null)
1535     {
1536       /*
1537        * Start the applet immediately or show a button to start it
1538        */
1539       if (FALSE.equalsIgnoreCase(getParameter("showbutton")))
1540       {
1541         LoadingThread loader = new LoadingThread(file, file2, this);
1542         loader.start();
1543       }
1544       else
1545       {
1546         add(launcher);
1547         launcher.addActionListener(new java.awt.event.ActionListener()
1548         {
1549           @Override
1550           public void actionPerformed(ActionEvent e)
1551           {
1552             LoadingThread loader = new LoadingThread(file, file2,
1553                     JalviewLite.this);
1554             loader.start();
1555           }
1556         });
1557       }
1558     }
1559     else
1560     {
1561       // jalview initialisation with no alignment. loadAlignment() method can
1562       // still be called to open new alignments.
1563       file = "NO FILE";
1564       fileFound = false;
1565       callInitCallback();
1566     }
1567   }
1568
1569   private void initLiveConnect()
1570   {
1571     // try really hard to get the liveConnect thing working
1572     boolean notFailed = false;
1573     int tries = 0;
1574     while (!notFailed && tries < 10)
1575     {
1576       if (tries > 0)
1577       {
1578         System.err.println("LiveConnect request thread going to sleep.");
1579       }
1580       try
1581       {
1582         Thread.sleep(700 * (1 + tries));
1583       } catch (InterruptedException q)
1584       {
1585       }
1586       ;
1587       if (tries++ > 0)
1588       {
1589         System.err.println("LiveConnect request thread woken up.");
1590       }
1591       try
1592       {
1593         JSObject scriptObject = JSObject.getWindow(this);
1594         if (scriptObject.eval("navigator") != null)
1595         {
1596           notFailed = true;
1597         }
1598       } catch (Exception jsex)
1599       {
1600         System.err.println("Attempt " + tries
1601                 + " to access LiveConnect javascript failed.");
1602       }
1603     }
1604   }
1605
1606   private void callInitCallback()
1607   {
1608     String initjscallback = getParameter("oninit");
1609     if (initjscallback == null)
1610     {
1611       return;
1612     }
1613     initjscallback = initjscallback.trim();
1614     if (initjscallback.length() > 0)
1615     {
1616       JSObject scriptObject = null;
1617       try
1618       {
1619         scriptObject = JSObject.getWindow(this);
1620       } catch (Exception ex)
1621       {
1622       }
1623       ;
1624       // try really hard to let the browser plugin know we want liveconnect
1625       initLiveConnect();
1626
1627       if (scriptObject != null)
1628       {
1629         try
1630         {
1631           // do onInit with the JS executor thread
1632           new JSFunctionExec(this).executeJavascriptFunction(true,
1633                   initjscallback, null,
1634                   "Calling oninit callback '" + initjscallback + "'.");
1635         } catch (Exception e)
1636         {
1637           System.err.println("Exception when executing _oninit callback '"
1638                   + initjscallback + "'.");
1639           e.printStackTrace();
1640         }
1641       }
1642       else
1643       {
1644         System.err.println("Not executing _oninit callback '"
1645                 + initjscallback + "' - no scripting allowed.");
1646       }
1647     }
1648   }
1649
1650   /**
1651    * Initialises and displays a new java.awt.Frame
1652    * 
1653    * @param frame
1654    *          java.awt.Frame to be displayed
1655    * @param title
1656    *          title of new frame
1657    * @param width
1658    *          width if new frame
1659    * @param height
1660    *          height of new frame
1661    */
1662   public static void addFrame(final Frame frame, String title, int width,
1663           int height)
1664   {
1665     frame.setLocation(lastFrameX, lastFrameY);
1666     lastFrameX += 40;
1667     lastFrameY += 40;
1668     frame.setSize(width, height);
1669     frame.setTitle(title);
1670     frame.addWindowListener(new WindowAdapter()
1671     {
1672       @Override
1673       public void windowClosing(WindowEvent e)
1674       {
1675         if (frame instanceof AlignFrame)
1676         {
1677           AlignViewport vp = ((AlignFrame) frame).viewport;
1678           ((AlignFrame) frame).closeMenuItem_actionPerformed();
1679           if (vp.applet.currentAlignFrame == frame)
1680           {
1681             vp.applet.currentAlignFrame = null;
1682           }
1683           vp.applet = null;
1684           vp = null;
1685
1686         }
1687         lastFrameX -= 40;
1688         lastFrameY -= 40;
1689         if (frame instanceof EmbmenuFrame)
1690         {
1691           ((EmbmenuFrame) frame).destroyMenus();
1692         }
1693         frame.setMenuBar(null);
1694         frame.dispose();
1695       }
1696
1697       @Override
1698       public void windowActivated(WindowEvent e)
1699       {
1700         if (frame instanceof AlignFrame)
1701         {
1702           ((AlignFrame) frame).viewport.applet.currentAlignFrame = (AlignFrame) frame;
1703           if (debug)
1704           {
1705             System.err.println("Activated window " + frame);
1706           }
1707         }
1708         // be good.
1709         super.windowActivated(e);
1710       }
1711       /*
1712        * Probably not necessary to do this - see TODO above. (non-Javadoc)
1713        * 
1714        * @see
1715        * java.awt.event.WindowAdapter#windowDeactivated(java.awt.event.WindowEvent
1716        * )
1717        * 
1718        * public void windowDeactivated(WindowEvent e) { if (currentAlignFrame ==
1719        * frame) { currentAlignFrame = null; if (debug) {
1720        * System.err.println("Deactivated window "+frame); } }
1721        * super.windowDeactivated(e); }
1722        */
1723     });
1724     frame.setVisible(true);
1725   }
1726
1727   /**
1728    * This paints the background surrounding the "Launch Jalview button" <br>
1729    * <br>
1730    * If file given in parameter not found, displays error message
1731    * 
1732    * @param g
1733    *          graphics context
1734    */
1735   @Override
1736   public void paint(Graphics g)
1737   {
1738     if (!fileFound)
1739     {
1740       g.setColor(new Color(200, 200, 200));
1741       g.setColor(Color.cyan);
1742       g.fillRect(0, 0, getSize().width, getSize().height);
1743       g.setColor(Color.red);
1744       g.drawString(
1745               MessageManager.getString("label.jalview_cannot_open_file"), 5,
1746               15);
1747       g.drawString("\"" + file + "\"", 5, 30);
1748     }
1749     else if (embedded)
1750     {
1751       g.setColor(Color.black);
1752       g.setFont(new Font("Arial", Font.BOLD, 24));
1753       g.drawString(MessageManager.getString("label.jalview_applet"), 50,
1754               getSize().height / 2 - 30);
1755       g.drawString(MessageManager.getString("label.loading_data") + "...",
1756               50, getSize().height / 2);
1757     }
1758   }
1759
1760   /**
1761    * get all components associated with the applet of the given type
1762    * 
1763    * @param class1
1764    * @return
1765    */
1766   public Vector getAppletWindow(Class class1)
1767   {
1768     Vector wnds = new Vector();
1769     Component[] cmp = getComponents();
1770     if (cmp != null)
1771     {
1772       for (int i = 0; i < cmp.length; i++)
1773       {
1774         if (class1.isAssignableFrom(cmp[i].getClass()))
1775         {
1776           wnds.addElement(cmp);
1777         }
1778       }
1779     }
1780     return wnds;
1781   }
1782
1783   class LoadJmolThread extends Thread
1784   {
1785     private boolean running = false;
1786
1787     @Override
1788     public void run()
1789     {
1790       if (running || checkedForJmol)
1791       {
1792         return;
1793       }
1794       running = true;
1795       if (checkForJmol)
1796       {
1797         try
1798         {
1799           if (!System.getProperty("java.version").startsWith("1.1"))
1800           {
1801             Class.forName("org.jmol.adapter.smarter.SmarterJmolAdapter");
1802             jmolAvailable = true;
1803           }
1804           if (!jmolAvailable)
1805           {
1806             System.out.println(
1807                     "Jmol not available - Using mc_view for structures");
1808           }
1809         } catch (java.lang.ClassNotFoundException ex)
1810         {
1811         }
1812       }
1813       else
1814       {
1815         jmolAvailable = false;
1816         if (debug)
1817         {
1818           System.err.println(
1819                   "Skipping Jmol check. Will use mc_view (probably)");
1820         }
1821       }
1822       checkedForJmol = true;
1823       running = false;
1824     }
1825
1826     public boolean notFinished()
1827     {
1828       return running || !checkedForJmol;
1829     }
1830   }
1831
1832   class LoadingThread extends Thread
1833   {
1834     /**
1835      * State variable: protocol for access to file source
1836      */
1837     DataSourceType protocol;
1838
1839     String _file; // alignment file or URL spec
1840
1841     String _file2; // second alignment file or URL spec
1842
1843     JalviewLite applet;
1844
1845     private void dbgMsg(String msg)
1846     {
1847       if (JalviewLite.debug)
1848       {
1849         System.err.println(msg);
1850       }
1851     }
1852
1853     /**
1854      * update the protocol state variable for accessing the datasource located
1855      * by file.
1856      * 
1857      * @param path
1858      * @return possibly updated datasource string
1859      */
1860     public String resolveFileProtocol(String path)
1861     {
1862       /*
1863        * is it paste data?
1864        */
1865       if (path.startsWith("PASTE"))
1866       {
1867         protocol = DataSourceType.PASTE;
1868         return path.substring(5);
1869       }
1870
1871       /*
1872        * is it a URL?
1873        */
1874       if (path.indexOf("://") != -1)
1875       {
1876         protocol = DataSourceType.URL;
1877         return path;
1878       }
1879
1880       /*
1881        * try relative to document root
1882        */
1883       URL documentBase = getDocumentBase();
1884       String withDocBase = resolveUrlForLocalOrAbsolute(path, documentBase);
1885       if (HttpUtils.isValidUrl(withDocBase))
1886       {
1887         if (debug)
1888         {
1889           System.err.println("Prepended document base '" + documentBase
1890                   + "' to make: '" + withDocBase + "'");
1891         }
1892         protocol = DataSourceType.URL;
1893         return withDocBase;
1894       }
1895
1896       /*
1897        * try relative to codebase (if different to document base)
1898        */
1899       URL codeBase = getCodeBase();
1900       String withCodeBase = applet.resolveUrlForLocalOrAbsolute(path,
1901               codeBase);
1902       if (!withCodeBase.equals(withDocBase)
1903               && HttpUtils.isValidUrl(withCodeBase))
1904       {
1905         protocol = DataSourceType.URL;
1906         if (debug)
1907         {
1908           System.err.println("Prepended codebase '" + codeBase
1909                   + "' to make: '" + withCodeBase + "'");
1910         }
1911         return withCodeBase;
1912       }
1913
1914       /*
1915        * try locating by classloader; try this last so files in the directory
1916        * are resolved using document base
1917        */
1918       if (inArchive(path))
1919       {
1920         protocol = DataSourceType.CLASSLOADER;
1921       }
1922       return path;
1923     }
1924
1925     public LoadingThread(String file, String file2, JalviewLite _applet)
1926     {
1927       this._file = file;
1928       this._file2 = file2;
1929       applet = _applet;
1930     }
1931
1932     @Override
1933     public void run()
1934     {
1935       LoadJmolThread jmolchecker = new LoadJmolThread();
1936       jmolchecker.start();
1937       while (jmolchecker.notFinished())
1938       {
1939         // wait around until the Jmol check is complete.
1940         try
1941         {
1942           Thread.sleep(2);
1943         } catch (Exception e)
1944         {
1945         }
1946       }
1947       startLoading();
1948       // applet.callInitCallback();
1949     }
1950
1951     /**
1952      * Load the alignment and any related files as specified by applet
1953      * parameters
1954      */
1955     private void startLoading()
1956     {
1957       dbgMsg("Loading thread started with:\n>>file\n" + _file
1958               + ">>endfile");
1959
1960       dbgMsg("Loading started.");
1961
1962       AlignFrame newAlignFrame = readAlignment(_file);
1963       AlignFrame newAlignFrame2 = readAlignment(_file2);
1964       if (newAlignFrame != null)
1965       {
1966         addToDisplay(newAlignFrame, newAlignFrame2);
1967         loadTree(newAlignFrame);
1968
1969         loadScoreFile(newAlignFrame);
1970
1971         loadFeatures(newAlignFrame);
1972
1973         loadAnnotations(newAlignFrame);
1974
1975         loadJnetFile(newAlignFrame);
1976
1977         loadPdbFiles(newAlignFrame);
1978       }
1979       else
1980       {
1981         fileFound = false;
1982         applet.remove(launcher);
1983         applet.repaint();
1984       }
1985       callInitCallback();
1986     }
1987
1988     /**
1989      * Add an AlignFrame to the display; or if two are provided, a SplitFrame.
1990      * 
1991      * @param af
1992      * @param af2
1993      */
1994     public void addToDisplay(AlignFrame af, AlignFrame af2)
1995     {
1996       if (af2 != null)
1997       {
1998         AlignmentI al1 = af.viewport.getAlignment();
1999         AlignmentI al2 = af2.viewport.getAlignment();
2000         AlignmentI cdna = al1.isNucleotide() ? al1 : al2;
2001         AlignmentI prot = al1.isNucleotide() ? al2 : al1;
2002         if (AlignmentUtils.mapProteinAlignmentToCdna(prot, cdna))
2003         {
2004           al2.alignAs(al1);
2005           SplitFrame sf = new SplitFrame(af, af2);
2006           sf.addToDisplay(embedded, JalviewLite.this);
2007           return;
2008         }
2009         else
2010         {
2011           String msg = "Could not map any sequence in " + af2.getTitle()
2012                   + " as "
2013                   + (al1.isNucleotide() ? "protein product" : "cDNA")
2014                   + " for " + af.getTitle();
2015           System.err.println(msg);
2016         }
2017       }
2018
2019       af.addToDisplay(embedded);
2020     }
2021
2022     /**
2023      * Read the alignment file (from URL, text 'paste', or archive by
2024      * classloader).
2025      * 
2026      * @return
2027      */
2028     protected AlignFrame readAlignment(String fileParam)
2029     {
2030       if (fileParam == null)
2031       {
2032         return null;
2033       }
2034       String resolvedFile = resolveFileProtocol(fileParam);
2035       AlignmentI al = null;
2036       try
2037       {
2038         FileFormatI format = new IdentifyFile().identify(resolvedFile,
2039                 protocol);
2040         dbgMsg("File identified as '" + format + "'");
2041         al = new AppletFormatAdapter().readFile(resolvedFile, protocol,
2042                 format);
2043         if ((al != null) && (al.getHeight() > 0))
2044         {
2045           dbgMsg("Successfully loaded file.");
2046           al.setDataset(null);
2047           AlignFrame newAlignFrame = new AlignFrame(al, applet,
2048                   resolvedFile, embedded, false);
2049           newAlignFrame.setTitle(resolvedFile);
2050           if (initialAlignFrame == null)
2051           {
2052             initialAlignFrame = newAlignFrame;
2053           }
2054           // update the focus.
2055           currentAlignFrame = newAlignFrame;
2056
2057           if (protocol == DataSourceType.PASTE)
2058           {
2059             newAlignFrame.setTitle(MessageManager
2060                     .formatMessage("label.sequences_from", new Object[]
2061                     { applet.getDocumentBase().toString() }));
2062           }
2063
2064           newAlignFrame.statusBar.setText(MessageManager.formatMessage(
2065                   "label.successfully_loaded_file", new Object[]
2066                   { resolvedFile }));
2067
2068           return newAlignFrame;
2069         }
2070       } catch (java.io.IOException ex)
2071       {
2072         dbgMsg("File load exception.");
2073         ex.printStackTrace();
2074         if (debug)
2075         {
2076           try
2077           {
2078             FileParse fp = new FileParse(resolvedFile, protocol);
2079             String ln = null;
2080             dbgMsg(">>>Dumping contents of '" + resolvedFile + "' " + "("
2081                     + protocol + ")");
2082             while ((ln = fp.nextLine()) != null)
2083             {
2084               dbgMsg(ln);
2085             }
2086             dbgMsg(">>>Dump finished.");
2087           } catch (Exception e)
2088           {
2089             System.err.println(
2090                     "Exception when trying to dump the content of the file parameter.");
2091             e.printStackTrace();
2092           }
2093         }
2094       }
2095       return null;
2096     }
2097
2098     /**
2099      * Load PDBFiles if any specified by parameter(s). Returns true if loaded,
2100      * else false.
2101      * 
2102      * @param alignFrame
2103      * @return
2104      */
2105     protected boolean loadPdbFiles(AlignFrame alignFrame)
2106     {
2107       boolean result = false;
2108       /*
2109        * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6 -
2110        * related to JAL-434
2111        */
2112
2113       applet.setAlignPdbStructures(
2114               getDefaultParameter("alignpdbfiles", false));
2115       /*
2116        * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
2117        * PDB|1GAQ|1GAQ|C">
2118        * 
2119        * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
2120        * 
2121        * <param name="PDBfile3" value="1q0o Q45135_9MICO">
2122        */
2123
2124       int pdbFileCount = 0;
2125       // Accumulate pdbs here if they are heading for the same view (if
2126       // alignPdbStructures is true)
2127       Vector pdbs = new Vector();
2128       // create a lazy matcher if we're asked to
2129       jalview.analysis.SequenceIdMatcher matcher = (applet
2130               .getDefaultParameter("relaxedidmatch", false))
2131                       ? new jalview.analysis.SequenceIdMatcher(
2132                               alignFrame.getAlignViewport().getAlignment()
2133                                       .getSequencesArray())
2134                       : null;
2135
2136       String param;
2137       do
2138       {
2139         if (pdbFileCount > 0)
2140         {
2141           param = applet.getParameter("PDBFILE" + pdbFileCount);
2142         }
2143         else
2144         {
2145           param = applet.getParameter("PDBFILE");
2146         }
2147
2148         if (param != null)
2149         {
2150           PDBEntry pdb = new PDBEntry();
2151
2152           String seqstring;
2153           SequenceI[] seqs = null;
2154           String[] chains = null;
2155
2156           StringTokenizer st = new StringTokenizer(param, " ");
2157
2158           if (st.countTokens() < 2)
2159           {
2160             String sequence = applet.getParameter("PDBSEQ");
2161             if (sequence != null)
2162             {
2163               seqs = new SequenceI[] { matcher == null
2164                       ? (Sequence) alignFrame.getAlignViewport()
2165                               .getAlignment().findName(sequence)
2166                       : matcher.findIdMatch(sequence) };
2167             }
2168
2169           }
2170           else
2171           {
2172             param = st.nextToken();
2173             List<SequenceI> tmp = new ArrayList<>();
2174             List<String> tmp2 = new ArrayList<>();
2175
2176             while (st.hasMoreTokens())
2177             {
2178               seqstring = st.nextToken();
2179               StringTokenizer st2 = new StringTokenizer(seqstring, "=");
2180               if (st2.countTokens() > 1)
2181               {
2182                 // This is the chain
2183                 tmp2.add(st2.nextToken());
2184                 seqstring = st2.nextToken();
2185               }
2186               tmp.add(matcher == null
2187                       ? (Sequence) alignFrame.getAlignViewport()
2188                               .getAlignment().findName(seqstring)
2189                       : matcher.findIdMatch(seqstring));
2190             }
2191
2192             seqs = tmp.toArray(new SequenceI[tmp.size()]);
2193             if (tmp2.size() == tmp.size())
2194             {
2195               chains = tmp2.toArray(new String[tmp2.size()]);
2196             }
2197           }
2198           param = resolveFileProtocol(param);
2199           // TODO check JAL-357 for files in a jar (CLASSLOADER)
2200           pdb.setFile(param);
2201
2202           if (seqs != null)
2203           {
2204             for (int i = 0; i < seqs.length; i++)
2205             {
2206               if (seqs[i] != null)
2207               {
2208                 ((Sequence) seqs[i]).addPDBId(pdb);
2209                 StructureSelectionManager
2210                         .getStructureSelectionManager(applet)
2211                         .registerPDBEntry(pdb);
2212               }
2213               else
2214               {
2215                 if (JalviewLite.debug)
2216                 {
2217                   // this may not really be a problem but we give a warning
2218                   // anyway
2219                   System.err.println(
2220                           "Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "
2221                                   + i + ")");
2222                 }
2223               }
2224             }
2225
2226             if (!alignPdbStructures)
2227             {
2228               alignFrame.newStructureView(applet, pdb, seqs, chains,
2229                       protocol);
2230             }
2231             else
2232             {
2233               pdbs.addElement(new Object[] { pdb, seqs, chains, protocol });
2234             }
2235           }
2236         }
2237
2238         pdbFileCount++;
2239       } while (param != null || pdbFileCount < 10);
2240       if (pdbs.size() > 0)
2241       {
2242         SequenceI[][] seqs = new SequenceI[pdbs.size()][];
2243         PDBEntry[] pdb = new PDBEntry[pdbs.size()];
2244         String[][] chains = new String[pdbs.size()][];
2245         String[] protocols = new String[pdbs.size()];
2246         for (int pdbsi = 0, pdbsiSize = pdbs
2247                 .size(); pdbsi < pdbsiSize; pdbsi++)
2248         {
2249           Object[] o = (Object[]) pdbs.elementAt(pdbsi);
2250           pdb[pdbsi] = (PDBEntry) o[0];
2251           seqs[pdbsi] = (SequenceI[]) o[1];
2252           chains[pdbsi] = (String[]) o[2];
2253           protocols[pdbsi] = (String) o[3];
2254         }
2255         alignFrame.alignedStructureView(applet, pdb, seqs, chains,
2256                 protocols);
2257         result = true;
2258       }
2259       return result;
2260     }
2261
2262     /**
2263      * Load in a Jnetfile if specified by parameter. Returns true if loaded,
2264      * else false.
2265      * 
2266      * @param alignFrame
2267      * @return
2268      */
2269     protected boolean loadJnetFile(AlignFrame alignFrame)
2270     {
2271       boolean result = false;
2272       String param = applet.getParameter("jnetfile");
2273       if (param == null)
2274       {
2275         // jnet became jpred around 2016
2276         param = applet.getParameter("jpredfile");
2277       }
2278       if (param != null)
2279       {
2280         try
2281         {
2282           param = resolveFileProtocol(param);
2283           JPredFile predictions = new JPredFile(param, protocol);
2284           JnetAnnotationMaker.add_annotation(predictions,
2285                   alignFrame.viewport.getAlignment(), 0, false);
2286           // false == do not add sequence profile from concise output
2287
2288           alignFrame.viewport.getAlignment().setupJPredAlignment();
2289
2290           alignFrame.alignPanel.fontChanged();
2291           alignFrame.alignPanel.setScrollValues(0, 0);
2292           result = true;
2293         } catch (Exception ex)
2294         {
2295           ex.printStackTrace();
2296         }
2297       }
2298       return result;
2299     }
2300
2301     /**
2302      * Load annotations if specified by parameter. Returns true if loaded, else
2303      * false.
2304      * 
2305      * @param alignFrame
2306      * @return
2307      */
2308     protected boolean loadAnnotations(AlignFrame alignFrame)
2309     {
2310       boolean result = false;
2311       String param = applet.getParameter("annotations");
2312       if (param != null)
2313       {
2314         param = resolveFileProtocol(param);
2315
2316         if (new AnnotationFile().annotateAlignmentView(alignFrame.viewport,
2317                 param, protocol))
2318         {
2319           alignFrame.alignPanel.fontChanged();
2320           alignFrame.alignPanel.setScrollValues(0, 0);
2321           result = true;
2322         }
2323         else
2324         {
2325           System.err.println(
2326                   "Annotations were not added from annotation file '"
2327                           + param + "'");
2328         }
2329       }
2330       return result;
2331     }
2332
2333     /**
2334      * Load features file and view settings as specified by parameters. Returns
2335      * true if features were loaded, else false.
2336      * 
2337      * @param alignFrame
2338      * @return
2339      */
2340     protected boolean loadFeatures(AlignFrame alignFrame)
2341     {
2342       boolean result = false;
2343       // ///////////////////////////
2344       // modify display of features
2345       // we do this before any features have been loaded, ensuring any hidden
2346       // groups are hidden when features first displayed
2347       //
2348       // hide specific groups
2349       //
2350       String param = applet.getParameter("hidefeaturegroups");
2351       if (param != null)
2352       {
2353         alignFrame.setFeatureGroupState(separatorListToArray(param), false);
2354         // applet.setFeatureGroupStateOn(newAlignFrame, param, false);
2355       }
2356       // show specific groups
2357       param = applet.getParameter("showfeaturegroups");
2358       if (param != null)
2359       {
2360         alignFrame.setFeatureGroupState(separatorListToArray(param), true);
2361         // applet.setFeatureGroupStateOn(newAlignFrame, param, true);
2362       }
2363       // and now load features
2364       param = applet.getParameter("features");
2365       if (param != null)
2366       {
2367         param = resolveFileProtocol(param);
2368
2369         result = alignFrame.parseFeaturesFile(param, protocol);
2370       }
2371
2372       param = applet.getParameter("showFeatureSettings");
2373       if (param != null && param.equalsIgnoreCase(TRUE))
2374       {
2375         alignFrame.viewport.setShowSequenceFeatures(true);
2376         new FeatureSettings(alignFrame.alignPanel);
2377       }
2378       return result;
2379     }
2380
2381     /**
2382      * Load a score file if specified by parameter. Returns true if file was
2383      * loaded, else false.
2384      * 
2385      * @param alignFrame
2386      */
2387     protected boolean loadScoreFile(AlignFrame alignFrame)
2388     {
2389       boolean result = false;
2390       String sScoreFile = applet.getParameter("scoreFile");
2391       if (sScoreFile != null && !"".equals(sScoreFile))
2392       {
2393         try
2394         {
2395           if (debug)
2396           {
2397             System.err.println(
2398                     "Attempting to load T-COFFEE score file from the scoreFile parameter");
2399           }
2400           result = alignFrame.loadScoreFile(sScoreFile);
2401           if (!result)
2402           {
2403             System.err.println(
2404                     "Failed to parse T-COFFEE parameter as a valid score file ('"
2405                             + sScoreFile + "')");
2406           }
2407         } catch (Exception e)
2408         {
2409           System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
2410                   sScoreFile, e.getMessage());
2411         }
2412       }
2413       return result;
2414     }
2415
2416     /**
2417      * Load a tree for the alignment if specified by parameter. Returns true if
2418      * a tree was loaded, else false.
2419      * 
2420      * @param alignFrame
2421      * @return
2422      */
2423     protected boolean loadTree(AlignFrame alignFrame)
2424     {
2425       boolean result = false;
2426       String treeFile = applet.getParameter("tree");
2427       if (treeFile == null)
2428       {
2429         treeFile = applet.getParameter("treeFile");
2430       }
2431
2432       if (treeFile != null)
2433       {
2434         try
2435         {
2436           treeFile = resolveFileProtocol(treeFile);
2437           NewickFile fin = new NewickFile(treeFile, protocol);
2438           fin.parse();
2439
2440           if (fin.getTree() != null)
2441           {
2442             alignFrame.loadTree(fin, treeFile);
2443             result = true;
2444             dbgMsg("Successfully imported tree.");
2445           }
2446           else
2447           {
2448             dbgMsg("Tree parameter did not resolve to a valid tree.");
2449           }
2450         } catch (Exception ex)
2451         {
2452           ex.printStackTrace();
2453         }
2454       }
2455       return result;
2456     }
2457
2458     /**
2459      * Discovers whether the given file is in the Applet Archive
2460      * 
2461      * @param f
2462      *          String
2463      * @return boolean
2464      */
2465     boolean inArchive(String f)
2466     {
2467       // This might throw a security exception in certain browsers
2468       // Netscape Communicator for instance.
2469       try
2470       {
2471         boolean rtn = (getClass().getResourceAsStream("/" + f) != null);
2472         if (debug)
2473         {
2474           System.err.println("Resource '" + f + "' was "
2475                   + (rtn ? "" : "not ") + "located by classloader.");
2476         }
2477         return rtn;
2478       } catch (Exception ex)
2479       {
2480         System.out.println("Exception checking resources: " + f + " " + ex);
2481         return false;
2482       }
2483     }
2484   }
2485
2486   /**
2487    * @return the default alignFrame acted on by the public applet methods. May
2488    *         return null with an error message on System.err indicating the
2489    *         fact.
2490    */
2491   public AlignFrame getDefaultTargetFrame()
2492   {
2493     if (currentAlignFrame != null)
2494     {
2495       return currentAlignFrame;
2496     }
2497     if (initialAlignFrame != null)
2498     {
2499       return initialAlignFrame;
2500     }
2501     System.err.println(
2502             "Implementation error: Jalview Applet API cannot work out which AlignFrame to use.");
2503     return null;
2504   }
2505
2506   /**
2507    * separator used for separatorList
2508    */
2509   protected String separator = "" + ((char) 0x00AC); // the default used to be
2510                                                      // '|' but many sequence
2511                                                      // IDS include pipes.
2512
2513   /**
2514    * set to enable the URL based javascript execution mechanism
2515    */
2516   public boolean jsfallbackEnabled = false;
2517
2518   /**
2519    * parse the string into a list
2520    * 
2521    * @param list
2522    * @return elements separated by separator
2523    */
2524   public String[] separatorListToArray(String list)
2525   {
2526     return separatorListToArray(list, separator);
2527   }
2528
2529   /**
2530    * parse the string into a list
2531    * 
2532    * @param list
2533    * @param separator
2534    * @return elements separated by separator
2535    */
2536   public static String[] separatorListToArray(String list, String separator)
2537   {
2538     // TODO use StringUtils version (slightly different...)
2539     int seplen = separator.length();
2540     if (list == null || list.equals("") || list.equals(separator))
2541     {
2542       return null;
2543     }
2544     java.util.Vector jv = new Vector();
2545     int cp = 0, pos;
2546     while ((pos = list.indexOf(separator, cp)) > cp)
2547     {
2548       jv.addElement(list.substring(cp, pos));
2549       cp = pos + seplen;
2550     }
2551     if (cp < list.length())
2552     {
2553       String c = list.substring(cp);
2554       if (!c.equals(separator))
2555       {
2556         jv.addElement(c);
2557       }
2558     }
2559     if (jv.size() > 0)
2560     {
2561       String[] v = new String[jv.size()];
2562       for (int i = 0; i < v.length; i++)
2563       {
2564         v[i] = (String) jv.elementAt(i);
2565       }
2566       jv.removeAllElements();
2567       if (debug)
2568       {
2569         System.err.println("Array from '" + separator
2570                 + "' separated List:\n" + v.length);
2571         for (int i = 0; i < v.length; i++)
2572         {
2573           System.err.println("item " + i + " '" + v[i] + "'");
2574         }
2575       }
2576       return v;
2577     }
2578     if (debug)
2579     {
2580       System.err.println(
2581               "Empty Array from '" + separator + "' separated List");
2582     }
2583     return null;
2584   }
2585
2586   /**
2587    * concatenate the list with separator
2588    * 
2589    * @param list
2590    * @return concatenated string
2591    */
2592   public String arrayToSeparatorList(String[] list)
2593   {
2594     return arrayToSeparatorList(list, separator);
2595   }
2596
2597   /**
2598    * concatenate the list with separator
2599    * 
2600    * @param list
2601    * @param separator
2602    * @return concatenated string
2603    */
2604   public static String arrayToSeparatorList(String[] list, String separator)
2605   {
2606     // TODO use StringUtils version
2607     StringBuffer v = new StringBuffer();
2608     if (list != null && list.length > 0)
2609     {
2610       for (int i = 0, iSize = list.length; i < iSize; i++)
2611       {
2612         if (list[i] != null)
2613         {
2614           if (i > 0)
2615           {
2616             v.append(separator);
2617           }
2618           v.append(list[i]);
2619         }
2620       }
2621       if (debug)
2622       {
2623         System.err
2624                 .println("Returning '" + separator + "' separated List:\n");
2625         System.err.println(v);
2626       }
2627       return v.toString();
2628     }
2629     if (debug)
2630     {
2631       System.err.println(
2632               "Returning empty '" + separator + "' separated List\n");
2633     }
2634     return "" + separator;
2635   }
2636
2637   /*
2638    * (non-Javadoc)
2639    * 
2640    * @see jalview.bin.JalviewLiteJsApi#getFeatureGroups()
2641    */
2642   @Override
2643   public String getFeatureGroups()
2644   {
2645     String lst = arrayToSeparatorList(
2646             getDefaultTargetFrame().getFeatureGroups());
2647     return lst;
2648   }
2649
2650   /*
2651    * (non-Javadoc)
2652    * 
2653    * @see
2654    * jalview.bin.JalviewLiteJsApi#getFeatureGroupsOn(jalview.appletgui.AlignFrame
2655    * )
2656    */
2657   @Override
2658   public String getFeatureGroupsOn(AlignFrame alf)
2659   {
2660     String lst = arrayToSeparatorList(alf.getFeatureGroups());
2661     return lst;
2662   }
2663
2664   /*
2665    * (non-Javadoc)
2666    * 
2667    * @see jalview.bin.JalviewLiteJsApi#getFeatureGroupsOfState(boolean)
2668    */
2669   @Override
2670   public String getFeatureGroupsOfState(boolean visible)
2671   {
2672     return arrayToSeparatorList(
2673             getDefaultTargetFrame().getFeatureGroupsOfState(visible));
2674   }
2675
2676   /*
2677    * (non-Javadoc)
2678    * 
2679    * @see
2680    * jalview.bin.JalviewLiteJsApi#getFeatureGroupsOfStateOn(jalview.appletgui
2681    * .AlignFrame, boolean)
2682    */
2683   @Override
2684   public String getFeatureGroupsOfStateOn(AlignFrame alf, boolean visible)
2685   {
2686     return arrayToSeparatorList(alf.getFeatureGroupsOfState(visible));
2687   }
2688
2689   /*
2690    * (non-Javadoc)
2691    * 
2692    * @see jalview.bin.JalviewLiteJsApi#setFeatureGroupStateOn(jalview.appletgui.
2693    * AlignFrame, java.lang.String, boolean)
2694    */
2695   @Override
2696   public void setFeatureGroupStateOn(final AlignFrame alf,
2697           final String groups, boolean state)
2698   {
2699     final boolean st = state;// !(state==null || state.equals("") ||
2700     // state.toLowerCase(Locale.ROOT).equals("false"));
2701     java.awt.EventQueue.invokeLater(new Runnable()
2702     {
2703       @Override
2704       public void run()
2705       {
2706         alf.setFeatureGroupState(separatorListToArray(groups), st);
2707       }
2708     });
2709   }
2710
2711   /*
2712    * (non-Javadoc)
2713    * 
2714    * @see jalview.bin.JalviewLiteJsApi#setFeatureGroupState(java.lang.String,
2715    * boolean)
2716    */
2717   @Override
2718   public void setFeatureGroupState(String groups, boolean state)
2719   {
2720     setFeatureGroupStateOn(getDefaultTargetFrame(), groups, state);
2721   }
2722
2723   /*
2724    * (non-Javadoc)
2725    * 
2726    * @see jalview.bin.JalviewLiteJsApi#getSeparator()
2727    */
2728   @Override
2729   public String getSeparator()
2730   {
2731     return separator;
2732   }
2733
2734   /*
2735    * (non-Javadoc)
2736    * 
2737    * @see jalview.bin.JalviewLiteJsApi#setSeparator(java.lang.String)
2738    */
2739   @Override
2740   public void setSeparator(String separator)
2741   {
2742     if (separator == null || separator.length() < 1)
2743     {
2744       // reset to default
2745       separator = "" + ((char) 0x00AC);
2746     }
2747     this.separator = separator;
2748     if (debug)
2749     {
2750       System.err.println("Default Separator now: '" + separator + "'");
2751     }
2752   }
2753
2754   /**
2755    * get boolean value of applet parameter 'name' and return default if
2756    * parameter is not set
2757    * 
2758    * @param name
2759    *          name of paremeter
2760    * @param def
2761    *          the value to return otherwise
2762    * @return true or false
2763    */
2764   public boolean getDefaultParameter(String name, boolean def)
2765   {
2766     String stn;
2767     if ((stn = getParameter(name)) == null)
2768     {
2769       return def;
2770     }
2771     if (TRUE.equalsIgnoreCase(stn))
2772     {
2773       return true;
2774     }
2775     return false;
2776   }
2777
2778   /*
2779    * (non-Javadoc)
2780    * 
2781    * @see jalview.bin.JalviewLiteJsApi#addPdbFile(jalview.appletgui.AlignFrame,
2782    * java.lang.String, java.lang.String, java.lang.String)
2783    */
2784   @Override
2785   public boolean addPdbFile(AlignFrame alFrame, String sequenceId,
2786           String pdbEntryString, String pdbFile)
2787   {
2788     return alFrame.addPdbFile(sequenceId, pdbEntryString, pdbFile);
2789   }
2790
2791   protected void setAlignPdbStructures(boolean alignPdbStructures)
2792   {
2793     this.alignPdbStructures = alignPdbStructures;
2794   }
2795
2796   public boolean isAlignPdbStructures()
2797   {
2798     return alignPdbStructures;
2799   }
2800
2801   @Override
2802   public void start()
2803   {
2804     // callInitCallback();
2805   }
2806
2807   private Hashtable<String, long[]> jshashes = new Hashtable<>();
2808
2809   private Hashtable<String, Hashtable<String, String[]>> jsmessages = new Hashtable<>();
2810
2811   public void setJsMessageSet(String messageclass, String viewId,
2812           String[] colcommands)
2813   {
2814     Hashtable<String, String[]> msgset = jsmessages.get(messageclass);
2815     if (msgset == null)
2816     {
2817       msgset = new Hashtable<>();
2818       jsmessages.put(messageclass, msgset);
2819     }
2820     msgset.put(viewId, colcommands);
2821     long[] l = new long[colcommands.length];
2822     for (int i = 0; i < colcommands.length; i++)
2823     {
2824       l[i] = colcommands[i].hashCode();
2825     }
2826     jshashes.put(messageclass + "|" + viewId, l);
2827   }
2828
2829   /*
2830    * (non-Javadoc)
2831    * 
2832    * @see jalview.bin.JalviewLiteJsApi#getJsMessage(java.lang.String,
2833    * java.lang.String)
2834    */
2835   @Override
2836   public String getJsMessage(String messageclass, String viewId)
2837   {
2838     Hashtable<String, String[]> msgset = jsmessages.get(messageclass);
2839     if (msgset != null)
2840     {
2841       String[] msgs = msgset.get(viewId);
2842       if (msgs != null)
2843       {
2844         for (int i = 0; i < msgs.length; i++)
2845         {
2846           if (msgs[i] != null)
2847           {
2848             String m = msgs[i];
2849             msgs[i] = null;
2850             return m;
2851           }
2852         }
2853       }
2854     }
2855     return "";
2856   }
2857
2858   public boolean isJsMessageSetChanged(String string, String string2,
2859           String[] colcommands)
2860   {
2861     long[] l = jshashes.get(string + "|" + string2);
2862     if (l == null && colcommands != null)
2863     {
2864       return true;
2865     }
2866     for (int i = 0; i < colcommands.length; i++)
2867     {
2868       if (l[i] != colcommands[i].hashCode())
2869       {
2870         return true;
2871       }
2872     }
2873     return false;
2874   }
2875
2876   private Vector jsExecQueue = new Vector();
2877
2878   public Vector getJsExecQueue()
2879   {
2880     return jsExecQueue;
2881   }
2882
2883   public void setExecutor(JSFunctionExec jsFunctionExec2)
2884   {
2885     jsFunctionExec = jsFunctionExec2;
2886   }
2887
2888   /**
2889    * return the given colour value parameter or the given default if parameter
2890    * not given
2891    * 
2892    * @param colparam
2893    * @param defcolour
2894    * @return
2895    */
2896   public Color getDefaultColourParameter(String colparam, Color defcolour)
2897   {
2898     String colprop = getParameter(colparam);
2899     if (colprop == null || colprop.trim().length() == 0)
2900     {
2901       return defcolour;
2902     }
2903     Color col = ColorUtils.parseColourString(colprop);
2904     if (col == null)
2905     {
2906       System.err.println("Couldn't parse '" + colprop + "' as a colour for "
2907               + colparam);
2908     }
2909     return (col == null) ? defcolour : col;
2910   }
2911
2912   public void openJalviewHelpUrl()
2913   {
2914     String helpUrl = getParameter("jalviewhelpurl");
2915     if (helpUrl == null || helpUrl.trim().length() < 5)
2916     {
2917       helpUrl = "http://www.jalview.org/help.html";
2918     }
2919     showURL(helpUrl, "HELP");
2920   }
2921
2922   /**
2923    * form a complete URL given a path to a resource and a reference location on
2924    * the same server
2925    * 
2926    * @param targetPath
2927    *          - an absolute path on the same server as localref or a document
2928    *          located relative to localref
2929    * @param localref
2930    *          - a URL on the same server as url
2931    * @return a complete URL for the resource located by url
2932    */
2933   private String resolveUrlForLocalOrAbsolute(String targetPath,
2934           URL localref)
2935   {
2936     String resolvedPath = "";
2937     if (targetPath.startsWith("/"))
2938     {
2939       String codebase = localref.toString();
2940       String localfile = localref.getFile();
2941       resolvedPath = codebase.substring(0,
2942               codebase.length() - localfile.length()) + targetPath;
2943       return resolvedPath;
2944     }
2945
2946     /*
2947      * get URL path and strip off any trailing file e.g.
2948      * www.jalview.org/examples/index.html#applets?a=b is trimmed to
2949      * www.jalview.org/examples/
2950      */
2951     String urlPath = localref.toString();
2952     String directoryPath = urlPath;
2953     int lastSeparator = directoryPath.lastIndexOf("/");
2954     if (lastSeparator > 0)
2955     {
2956       directoryPath = directoryPath.substring(0, lastSeparator + 1);
2957     }
2958
2959     if (targetPath.startsWith("/"))
2960     {
2961       /*
2962        * construct absolute URL to a file on the server - this is not allowed?
2963        */
2964       // String localfile = localref.getFile();
2965       // resolvedPath = urlPath.substring(0,
2966       // urlPath.length() - localfile.length())
2967       // + targetPath;
2968       resolvedPath = directoryPath + targetPath.substring(1);
2969     }
2970     else
2971     {
2972       resolvedPath = directoryPath + targetPath;
2973     }
2974     if (debug)
2975     {
2976       System.err.println(
2977               "resolveUrlForLocalOrAbsolute returning " + resolvedPath);
2978     }
2979     return resolvedPath;
2980   }
2981
2982   /**
2983    * open a URL in the browser - resolving it according to relative refs and
2984    * coping with javascript: protocol if necessary.
2985    * 
2986    * @param url
2987    * @param target
2988    */
2989   public void showURL(String url, String target)
2990   {
2991     try
2992     {
2993       if (url.indexOf(":") == -1)
2994       {
2995         // TODO: verify (Bas Vroling bug) prepend codebase or server URL to
2996         // form valid URL
2997         // Should really use docbase, not codebase.
2998         URL prepend;
2999         url = resolveUrlForLocalOrAbsolute(url,
3000                 prepend = getDefaultParameter("resolvetocodebase", false)
3001                         ? getCodeBase()
3002                         : getDocumentBase());
3003         if (debug)
3004         {
3005           System.err.println("Show url (prepended " + prepend
3006                   + " - toggle resolvetocodebase if code/docbase resolution is wrong): "
3007                   + url);
3008         }
3009       }
3010       else
3011       {
3012         if (debug)
3013         {
3014           System.err.println("Show url: " + url);
3015         }
3016       }
3017       if (url.indexOf("javascript:") == 0)
3018       {
3019         // no target for the javascript context
3020         getAppletContext().showDocument(new java.net.URL(url));
3021       }
3022       else
3023       {
3024         getAppletContext().showDocument(new java.net.URL(url), target);
3025       }
3026     } catch (Exception ex)
3027     {
3028       ex.printStackTrace();
3029     }
3030   }
3031
3032   /**
3033    * bind structures in a viewer to any matching sequences in an alignFrame (use
3034    * sequenceIds to limit scope of search to specific sequences)
3035    * 
3036    * @param alFrame
3037    * @param viewer
3038    * @param sequenceIds
3039    * @return TODO: consider making an exception structure for indicating when
3040    *         binding fails public SequenceStructureBinding
3041    *         addStructureViewInstance( AlignFrame alFrame, Object viewer, String
3042    *         sequenceIds) {
3043    * 
3044    *         if (sequenceIds != null && sequenceIds.length() > 0) { return
3045    *         alFrame.addStructureViewInstance(viewer,
3046    *         separatorListToArray(sequenceIds)); } else { return
3047    *         alFrame.addStructureViewInstance(viewer, null); } // return null; }
3048    */
3049 }