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