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