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