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