warn user that no JS callbacks will work by default, and show stacktrace if debug...
[jalview.git] / src / jalview / bin / JalviewLite.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.bin;
19
20 import jalview.appletgui.AlignFrame;
21 import jalview.appletgui.EmbmenuFrame;
22 import jalview.appletgui.FeatureSettings;
23 import jalview.datamodel.Alignment;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.ColumnSelection;
26 import jalview.datamodel.PDBEntry;
27 import jalview.datamodel.Sequence;
28 import jalview.datamodel.SequenceGroup;
29 import jalview.datamodel.SequenceI;
30 import jalview.io.AnnotationFile;
31 import jalview.io.AppletFormatAdapter;
32 import jalview.io.FileParse;
33 import jalview.io.IdentifyFile;
34 import jalview.io.JnetAnnotationMaker;
35 import jalview.javascript.JSFunctionExec;
36 import jalview.javascript.JsCallBack;
37 import jalview.structure.SelectionListener;
38 import jalview.structure.StructureSelectionManager;
39
40 import java.applet.Applet;
41 import java.awt.Button;
42 import java.awt.Color;
43 import java.awt.Component;
44 import java.awt.Font;
45 import java.awt.Frame;
46 import java.awt.Graphics;
47 import java.awt.event.ActionEvent;
48 import java.awt.event.WindowAdapter;
49 import java.awt.event.WindowEvent;
50 import java.io.BufferedReader;
51 import java.io.InputStreamReader;
52 import java.util.StringTokenizer;
53 import java.util.Vector;
54
55 import netscape.javascript.JSObject;
56
57 /**
58  * Jalview Applet. Runs in Java 1.18 runtime
59  * 
60  * @author $author$
61  * @version $Revision$
62  */
63 public class JalviewLite extends Applet
64 {
65
66   // /////////////////////////////////////////
67   // The following public methods maybe called
68   // externally, eg via javascript in HTML page
69   /**
70    * @return String list of selected sequence IDs, each terminated by the 'boolean not' character (""+0x00AC) or
71    *         (&#172;)
72    */
73   public String getSelectedSequences()
74   {
75     return getSelectedSequencesFrom(getDefaultTargetFrame());
76   }
77
78   /**
79    * @param sep
80    *          separator string or null for default
81    * @return String list of selected sequence IDs, each terminated by given separator string
82    */
83   public String getSelectedSequences(String sep)
84   {
85     return getSelectedSequencesFrom(getDefaultTargetFrame(), sep);
86   }
87
88   /**
89    * @param alf
90    *          alignframe containing selection
91    * @return String list of selected sequence IDs, each terminated by current default separator sequence
92    * 
93    */
94   public String getSelectedSequencesFrom(AlignFrame alf)
95   {
96     return getSelectedSequencesFrom(alf,separator); // ""+0x00AC);
97   }
98
99   /**
100    * get list of selected sequence IDs separated by given separator
101    * 
102    * @param alf
103    *          window containing selection
104    * @param sep
105    *          separator string to use - default is 'boolean not'
106    * @return String list of selected sequence IDs, each terminated by the given
107    *         separator
108    */
109   public String getSelectedSequencesFrom(AlignFrame alf, String sep)
110   {
111     StringBuffer result = new StringBuffer("");
112     if (sep == null || sep.length() == 0)
113     {
114       sep = separator; // "+0x00AC;
115     }
116     if (alf.viewport.getSelectionGroup() != null)
117     {
118       SequenceI[] seqs = alf.viewport.getSelectionGroup()
119               .getSequencesInOrder(alf.viewport.getAlignment());
120
121       for (int i = 0; i < seqs.length; i++)
122       {
123         result.append(seqs[i].getName());
124         result.append(sep);
125       }
126     }
127
128     return result.toString();
129   }
130
131   /**
132    * 
133    * @param sequenceId
134    *          id of sequence to highlight
135    * @param position
136    *          integer position [ tobe implemented or range ] on sequence
137    * @param alignedPosition
138    *          true/false/empty string - indicate if position is an alignment
139    *          column or unaligned sequence position
140    */
141   public void highlight(String sequenceId, String position,
142           String alignedPosition)
143   {
144     highlightIn(getDefaultTargetFrame(), sequenceId, position, alignedPosition);
145   }
146
147   /**
148    * 
149    * @param sequenceId
150    *          id of sequence to highlight
151    * @param position
152    *          integer position [ tobe implemented or range ] on sequence
153    * @param alignedPosition
154    *          false, blank or something else - indicate if position is an
155    *          alignment column or unaligned sequence position
156    */
157   public void highlightIn(AlignFrame alf, String sequenceId, String position,
158           String alignedPosition)
159   {
160     // TODO: could try to highlight in all alignments if alf==null
161     SequenceI sq = alf.getAlignViewport().getAlignment()
162             .findName(sequenceId);
163     if (sq != null)
164     {
165       int pos, apos = -1;
166       try
167       {
168         apos = new Integer(position).intValue();
169         apos--;
170       } catch (NumberFormatException ex)
171       {
172         return;
173       }
174       // use vamsas listener to broadcast to all listeners in scope
175       if (alignedPosition != null
176               && (alignedPosition.trim().length() == 0 || alignedPosition
177                       .toLowerCase().indexOf("false") > -1))
178       {
179         StructureSelectionManager.getStructureSelectionManager()
180                 .mouseOverVamsasSequence(sq, sq.findIndex(apos));
181       }
182       else
183       {
184         StructureSelectionManager.getStructureSelectionManager()
185                 .mouseOverVamsasSequence(sq, apos);
186       }
187
188     }
189   }
190
191   /**
192    * select regions of the currrent alignment frame
193    * 
194    * @param sequenceIds
195    *          String separated list of sequence ids or empty string
196    * @param columns
197    *          String separated list { column range or column, ..} or empty
198    *          string
199    */
200   public void select(String sequenceIds, String columns)
201   {
202     selectIn(getDefaultTargetFrame(), sequenceIds, columns, separator);
203   }
204
205   /**
206    * select regions of the currrent alignment frame
207    * 
208    * @param toselect
209    *          String separated list { column range, seq1...seqn sequence ids }
210    * @param sep
211    *          separator between toselect fields
212    */
213   public void select(String sequenceIds, String columns, String sep)
214   {
215     selectIn(getDefaultTargetFrame(), sequenceIds, columns, sep);
216   }
217
218   /**
219    * select regions of the given alignment frame
220    * 
221    * @param alf
222    * @param toselect
223    *          String separated list { column range, seq1...seqn sequence ids }
224    * @param sep
225    *          separator between toselect fields
226    */
227   public void selectIn(AlignFrame alf, String sequenceIds, String columns)
228   {
229     selectIn(alf, sequenceIds, columns, separator);
230   }
231
232   /**
233    * select regions of the given alignment frame
234    * 
235    * @param alf
236    * @param toselect
237    *          String separated list { column range, seq1...seqn sequence ids }
238    * @param sep
239    *          separator between toselect fields
240    */
241   public void selectIn(AlignFrame alf, String sequenceIds, String columns,
242           String sep)
243   {
244     if (sep == null || sep.length() == 0)
245     {
246       sep = separator;
247     } else {
248       if (debug)
249       {
250         System.err.println("Selecting region using separator string '"+separator+"'");
251       }
252     }
253     // deparse fields
254     String[] ids = separatorListToArray(sequenceIds, sep);
255     String[] cols = separatorListToArray(columns, sep);
256     SequenceGroup sel = new SequenceGroup();
257     ColumnSelection csel = new ColumnSelection();
258     AlignmentI al = alf.viewport.getAlignment();
259     int start = 0, end = al.getWidth(), alw = al.getWidth();
260     if (ids != null && ids.length > 0)
261     {
262       for (int i = 0; i < ids.length; i++)
263       {
264         if (ids[i].trim().length() == 0)
265         {
266           continue;
267         }
268         SequenceI sq = al.findName(ids[i]);
269         if (sq != null)
270         {
271           sel.addSequence(sq, false);
272         }
273       }
274     }
275     if (cols != null && cols.length > 0)
276     {
277       boolean seset = false;
278       for (int i = 0; i < cols.length; i++)
279       {
280         String cl = cols[i].trim();
281         if (cl.length() == 0)
282         {
283           continue;
284         }
285         int p;
286         if ((p = cl.indexOf("-")) > -1)
287         {
288           int from = -1, to = -1;
289           try
290           {
291             from = new Integer(cl.substring(0, p)).intValue();
292             from--;
293           } catch (NumberFormatException ex)
294           {
295             System.err
296                     .println("ERROR: Couldn't parse first integer in range element column selection string '"
297                             + cl + "' - format is 'from-to'");
298             return;
299           }
300           try
301           {
302             to = new Integer(cl.substring(p + 1)).intValue();
303             to--;
304           } catch (NumberFormatException ex)
305           {
306             System.err
307                     .println("ERROR: Couldn't parse second integer in range element column selection string '"
308                             + cl + "' - format is 'from-to'");
309             return;
310           }
311           if (from >= 0 && to >= 0)
312           {
313             // valid range
314             if (from < to)
315             {
316               int t = to;
317               to = from;
318               to = t;
319             }
320             if (!seset)
321             {
322               start = from;
323               end = to;
324               seset = true;
325             }
326             else
327             {
328               // comment to prevent range extension
329               if (start > from)
330               {
331                 start = from;
332               }
333               if (end < to)
334               {
335                 end = to;
336               }
337             }
338             for (int r = from; r <= to; r++)
339             {
340               if (r >= 0 && r < alw)
341               {
342                 csel.addElement(r);
343               }
344             }
345             if (debug)
346             {
347               System.err.println("Range '" + cl + "' deparsed as [" + from
348                       + "," + to + "]");
349             }
350           }
351           else
352           {
353             System.err.println("ERROR: Invalid Range '" + cl
354                     + "' deparsed as [" + from + "," + to + "]");
355           }
356         }
357         else
358         {
359           int r = -1;
360           try
361           {
362             r = new Integer(cl).intValue();
363             r--;
364           } catch (NumberFormatException ex)
365           {
366             System.err
367                     .println("ERROR: Couldn't parse integer from point selection element of column selection string '"
368                             + cl + "'");
369             return;
370           }
371           if (r >= 0 && r <= alw)
372           {
373             if (!seset)
374             {
375               start = r;
376               end = r;
377               seset = true;
378             }
379             else
380             {
381               // comment to prevent range extension
382               if (start > r)
383               {
384                 start = r;
385               }
386               if (end < r)
387               {
388                 end = r;
389               }
390             }
391             csel.addElement(r);
392             if (debug)
393             {
394               System.err.println("Point selection '" + cl
395                       + "' deparsed as [" + r + "]");
396             }
397           }
398           else
399           {
400             System.err.println("ERROR: Invalid Point selection '" + cl
401                     + "' deparsed as [" + r + "]");
402           }
403         }
404       }
405     }
406     sel.setStartRes(start);
407     sel.setEndRes(end);
408     alf.select(sel, csel);
409
410   }
411
412   /**
413    * get sequences selected in current alignFrame and return their alignment in
414    * format 'format' either with or without suffix
415    * 
416    * @param alf
417    *          - where selection is
418    * @param format
419    *          - format of alignment file
420    * @param suffix
421    *          - "true" to append /start-end string to each sequence ID
422    * @return selected sequences as flat file or empty string if there was no
423    *         current selection
424    */
425   public String getSelectedSequencesAsAlignment(String format, String suffix)
426   {
427     return getSelectedSequencesAsAlignmentFrom(getDefaultTargetFrame(), format,
428             suffix);
429   }
430
431   /**
432    * get sequences selected in alf and return their alignment in format 'format'
433    * either with or without suffix
434    * 
435    * @param alf
436    *          - where selection is
437    * @param format
438    *          - format of alignment file
439    * @param suffix
440    *          - "true" to append /start-end string to each sequence ID
441    * @return selected sequences as flat file or empty string if there was no
442    *         current selection
443    */
444   public String getSelectedSequencesAsAlignmentFrom(AlignFrame alf,
445           String format, String suffix)
446   {
447     try
448     {
449       boolean seqlimits = suffix.equalsIgnoreCase("true");
450       if (alf.viewport.getSelectionGroup() != null)
451       {
452         String reply = new AppletFormatAdapter().formatSequences(format,
453                 new Alignment(alf.viewport.getSelectionAsNewSequence()),
454                 seqlimits);
455         return reply;
456       }
457     } catch (Exception ex)
458     {
459       ex.printStackTrace();
460       return "Error retrieving alignment in " + format + " format. ";
461     }
462     return "";
463   }
464
465   public String getAlignment(String format)
466   {
467     return getAlignmentFrom(getDefaultTargetFrame(), format, "true");
468   }
469
470   public String getAlignmentFrom(AlignFrame alf, String format)
471   {
472     return getAlignmentFrom(alf, format, "true");
473   }
474
475   public String getAlignment(String format, String suffix)
476   {
477     return getAlignmentFrom(getDefaultTargetFrame(), format, suffix);
478   }
479
480   public String getAlignmentFrom(AlignFrame alf, String format,
481           String suffix)
482   {
483     try
484     {
485       boolean seqlimits = suffix.equalsIgnoreCase("true");
486
487       String reply = new AppletFormatAdapter().formatSequences(format,
488               alf.viewport.getAlignment(), seqlimits);
489       return reply;
490     } catch (Exception ex)
491     {
492       ex.printStackTrace();
493       return "Error retrieving alignment in " + format + " format. ";
494     }
495   }
496
497   public void loadAnnotation(String annotation)
498   {
499     loadAnnotationFrom(getDefaultTargetFrame(), annotation);
500   }
501
502   public void loadAnnotationFrom(AlignFrame alf, String annotation)
503   {
504     if (new AnnotationFile().readAnnotationFile(alf.getAlignViewport()
505             .getAlignment(), annotation, AppletFormatAdapter.PASTE))
506     {
507       alf.alignPanel.fontChanged();
508       alf.alignPanel.setScrollValues(0, 0);
509     }
510     else
511     {
512       alf.parseFeaturesFile(annotation, AppletFormatAdapter.PASTE);
513     }
514   }
515
516   public String getFeatures(String format)
517   {
518     return getFeaturesFrom(getDefaultTargetFrame(), format);
519   }
520
521   public String getFeaturesFrom(AlignFrame alf, String format)
522   {
523     return alf.outputFeatures(false, format);
524   }
525
526   public String getAnnotation()
527   {
528     return getAnnotationFrom(getDefaultTargetFrame());
529   }
530
531   public String getAnnotationFrom(AlignFrame alf)
532   {
533     return alf.outputAnnotations(false);
534   }
535
536   public AlignFrame newView()
537   {
538     return newViewFrom(getDefaultTargetFrame());
539   }
540
541   public AlignFrame newView(String name)
542   {
543     return newViewFrom(getDefaultTargetFrame(), name);
544   }
545
546   public AlignFrame newViewFrom(AlignFrame alf)
547   {
548     return alf.newView(null);
549   }
550
551   public AlignFrame newViewFrom(AlignFrame alf, String name)
552   {
553     return alf.newView(name);
554   }
555
556   /**
557    * 
558    * @param text
559    *          alignment file as a string
560    * @param title
561    *          window title
562    * @return null or new alignment frame
563    */
564   public AlignFrame loadAlignment(String text, String title)
565   {
566     Alignment al = null;
567
568     String format = new IdentifyFile().Identify(text,
569             AppletFormatAdapter.PASTE);
570     try
571     {
572       al = new AppletFormatAdapter().readFile(text,
573               AppletFormatAdapter.PASTE, format);
574       if (al.getHeight() > 0)
575       {
576         return new AlignFrame(al, this, title, false);
577       }
578     } catch (java.io.IOException ex)
579     {
580       ex.printStackTrace();
581     }
582     return null;
583   }
584
585   public void setMouseoverListener(String listener)
586   {
587     setMouseoverListener(currentAlignFrame, listener);
588   }
589
590   private Vector mouseoverListeners = new Vector();
591
592   public void setMouseoverListener(AlignFrame af, String listener)
593   {
594     if (listener != null)
595     {
596       listener = listener.trim();
597       if (listener.length() == 0)
598       {
599         System.err
600                 .println("jalview Javascript error: Ignoring empty function for mouseover listener.");
601         return;
602       }
603     }
604     jalview.javascript.MouseOverListener mol = new jalview.javascript.MouseOverListener(
605             this, af, listener);
606     mouseoverListeners.addElement(mol);
607     StructureSelectionManager.getStructureSelectionManager()
608             .addStructureViewerListener(mol);
609     if (debug)
610     {
611       System.err.println("Added a mouseover listener for "
612               + ((af == null) ? "All frames" : "Just views for "
613                       + af.getAlignViewport().getSequenceSetId()));
614       System.err.println("There are now " + mouseoverListeners.size()
615               + " listeners in total.");
616     }
617   }
618
619   public void setSelectionListener(String listener)
620   {
621     setSelectionListener(null, listener);
622   }
623
624   public void setSelectionListener(AlignFrame af, String listener)
625   {
626     if (listener != null)
627     {
628       listener = listener.trim();
629       if (listener.length() == 0)
630       {
631         System.err
632                 .println("jalview Javascript error: Ignoring empty function for selection listener.");
633         return;
634       }
635     }
636     jalview.javascript.JsSelectionSender mol = new jalview.javascript.JsSelectionSender(
637             this, af, listener);
638     mouseoverListeners.addElement(mol);
639     StructureSelectionManager.getStructureSelectionManager()
640             .addSelectionListener(mol);
641     if (debug)
642     {
643       System.err.println("Added a selection listener for "
644               + ((af == null) ? "All frames" : "Just views for "
645                       + af.getAlignViewport().getSequenceSetId()));
646       System.err.println("There are now " + mouseoverListeners.size()
647               + " listeners in total.");
648     }
649   }
650
651   /**
652    * remove any callback using the given listener function and associated with
653    * the given alignFrame (or null for all callbacks)
654    * 
655    * @param af
656    *          (may be null)
657    * @param listener
658    *          (may be null)
659    */
660   public void removeJavascriptListener(AlignFrame af, String listener)
661   {
662     if (listener != null)
663     {
664       listener = listener.trim();
665       if (listener.length() == 0)
666       {
667         listener = null;
668       }
669     }
670     boolean rprt = false;
671     for (int ms = 0, msSize = mouseoverListeners.size(); ms < msSize;)
672     {
673       Object lstn = mouseoverListeners.elementAt(ms);
674       JsCallBack lstner = (JsCallBack) lstn;
675       if ((af == null || lstner.getAlignFrame() == af)
676               && (listener == null || lstner.getListenerFunction().equals(
677                       listener)))
678       {
679         mouseoverListeners.removeElement(lstner);
680         msSize--;
681         if (lstner instanceof SelectionListener)
682         {
683           StructureSelectionManager.getStructureSelectionManager()
684                   .removeSelectionListener((SelectionListener) lstner);
685         }
686         else
687         {
688           StructureSelectionManager.getStructureSelectionManager()
689                   .removeStructureViewerListener(lstner, null);
690         }
691         rprt = debug;
692         if (debug)
693         {
694           System.err.println("Removed listener '" + listener + "'");
695         }
696       }
697       else
698       {
699         ms++;
700       }
701     }
702     if (rprt)
703     {
704       System.err.println("There are now " + mouseoverListeners.size()
705               + " listeners in total.");
706     }
707   }
708
709   public void stop()
710   {
711     if (mouseoverListeners != null)
712     {
713       while (mouseoverListeners.size() > 0)
714       {
715         Object mol = mouseoverListeners.elementAt(0);
716         mouseoverListeners.removeElement(mol);
717         if (mol instanceof SelectionListener)
718         {
719           StructureSelectionManager.getStructureSelectionManager()
720                   .removeSelectionListener((SelectionListener) mol);
721         }
722         else
723         {
724           StructureSelectionManager.getStructureSelectionManager()
725                   .removeStructureViewerListener(mol, null);
726         }
727       }
728     }
729     jalview.javascript.JSFunctionExec.stopQueue();
730   }
731
732   /**
733    * send a mouseover message to all the alignment windows associated with the
734    * given residue in the pdbfile
735    * 
736    * @param pdbResNum
737    * @param chain
738    * @param pdbfile
739    */
740   public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
741   {
742     StructureSelectionManager.getStructureSelectionManager()
743             .mouseOverStructure(pdbResNum, chain, pdbfile);
744   }
745
746   // //////////////////////////////////////////////
747   // //////////////////////////////////////////////
748
749   public static int lastFrameX = 200;
750
751   public static int lastFrameY = 200;
752
753   boolean fileFound = true;
754
755   String file = "No file";
756
757   Button launcher = new Button("Start Jalview");
758
759   /**
760    * The currentAlignFrame is static, it will change if and when the user
761    * selects a new window. Note that it will *never* point back to the embedded
762    * AlignFrame if the applet is started as embedded on the page and then
763    * afterwards a new view is created.
764    */
765   public AlignFrame currentAlignFrame = null;
766
767   /**
768    * This is the first frame to be displayed, and does not change. API calls
769    * will default to this instance if currentAlignFrame is null.
770    */
771   AlignFrame initialAlignFrame = null;
772
773   boolean embedded = false;
774
775   private boolean checkForJmol = true;
776
777   private boolean checkedForJmol = false; // ensure we don't check for jmol
778
779   // every time the app is re-inited
780
781   public boolean jmolAvailable = false;
782
783   private boolean alignPdbStructures = false;
784
785   public static boolean debug = false;
786
787   static String builddate = null, version = null;
788
789   private static void initBuildDetails()
790   {
791     if (builddate == null)
792     {
793       builddate = "unknown";
794       version = "test";
795       java.net.URL url = JalviewLite.class
796               .getResource("/.build_properties");
797       if (url != null)
798       {
799         try
800         {
801           BufferedReader reader = new BufferedReader(new InputStreamReader(
802                   url.openStream()));
803           String line;
804           while ((line = reader.readLine()) != null)
805           {
806             if (line.indexOf("VERSION") > -1)
807             {
808               version = line.substring(line.indexOf("=") + 1);
809             }
810             if (line.indexOf("BUILD_DATE") > -1)
811             {
812               builddate = line.substring(line.indexOf("=") + 1);
813             }
814           }
815         } catch (Exception ex)
816         {
817           ex.printStackTrace();
818         }
819       }
820     }
821   }
822
823   public static String getBuildDate()
824   {
825     initBuildDetails();
826     return builddate;
827   }
828
829   public static String getVersion()
830   {
831     initBuildDetails();
832     return version;
833   }
834
835   // public JSObject scriptObject = null;
836
837   /**
838    * init method for Jalview Applet
839    */
840   public void init()
841   {
842     // remove any handlers that might be hanging around from an earlier instance
843     try
844     {
845        JSObject scriptObject = JSObject.getWindow(this);       
846     } catch (Exception ex)
847     {
848       System.err
849       .println("Warning: No JalviewLite javascript callbacks available.");
850       if (debug)
851       {
852         ex.printStackTrace();
853       }
854     }
855     /**
856      * turn on extra applet debugging
857      */
858     String dbg = getParameter("debug");
859     if (dbg != null)
860     {
861       debug = dbg.toLowerCase().equals("true");
862     }
863     if (debug)
864     {
865
866       System.err.println("JalviewLite Version " + getVersion());
867       System.err.println("Build Date : " + getBuildDate());
868
869     }
870     /**
871      * if true disable the check for jmol
872      */
873     String chkforJmol = getParameter("nojmol");
874     if (chkforJmol != null)
875     {
876       checkForJmol = !chkforJmol.equals("true");
877     }
878     /**
879      * get the separator parameter if present
880      */
881     String sep = getParameter("separator");
882     if (sep != null)
883     {
884       if (sep.length() > 0)
885       {
886         separator = sep;
887         if (debug)
888         {
889           System.err.println("Separator set to '" + separator + "'");
890         }
891       }
892       else
893       {
894         throw new Error(
895                 "Invalid separator parameter - must be non-zero length");
896       }
897     }
898     int r = 255;
899     int g = 255;
900     int b = 255;
901     String param = getParameter("RGB");
902
903     if (param != null)
904     {
905       try
906       {
907         r = Integer.parseInt(param.substring(0, 2), 16);
908         g = Integer.parseInt(param.substring(2, 4), 16);
909         b = Integer.parseInt(param.substring(4, 6), 16);
910       } catch (Exception ex)
911       {
912         r = 255;
913         g = 255;
914         b = 255;
915       }
916     }
917     param = getParameter("label");
918     if (param != null)
919     {
920       launcher.setLabel(param);
921     }
922
923     setBackground(new Color(r, g, b));
924     
925     file = getParameter("file");
926
927     if (file == null)
928     {
929       // Maybe the sequences are added as parameters
930       StringBuffer data = new StringBuffer("PASTE");
931       int i = 1;
932       while ((file = getParameter("sequence" + i)) != null)
933       {
934         data.append(file.toString() + "\n");
935         i++;
936       }
937       if (data.length() > 5)
938       {
939         file = data.toString();
940       }
941     }
942
943     final JalviewLite jvapplet = this;
944     if (getParameter("embedded") != null
945             && getParameter("embedded").equalsIgnoreCase("true"))
946     {
947       // Launch as embedded applet in page
948       embedded = true;
949       LoadingThread loader = new LoadingThread(file, jvapplet);
950       loader.start();
951     }
952     else if (file != null)
953     {
954       if (getParameter("showbutton") == null
955               || !getParameter("showbutton").equalsIgnoreCase(
956                       "false"))
957       {
958         // Add the JalviewLite 'Button' to the page
959         add(launcher);
960         launcher.addActionListener(new java.awt.event.ActionListener()
961         {
962           public void actionPerformed(ActionEvent e)
963           {
964             LoadingThread loader = new LoadingThread(file, jvapplet);
965             loader.start();
966           }
967         });
968       }
969       else
970       {
971         // Open jalviewLite immediately.
972         LoadingThread loader = new LoadingThread(file, jvapplet);
973         loader.start();
974       }
975     }
976     else
977     {
978       // jalview initialisation with no alignment. loadAlignment() method can
979       // still be called to open new alignments.
980       file = "NO FILE";
981       fileFound = false;
982       // callInitCallback();
983     }
984   }
985
986   private void callInitCallback()
987   {
988     String initjscallback = getParameter("oninit");
989     if (initjscallback == null)
990     {
991       return;
992     }
993     initjscallback = initjscallback.trim();
994     if (initjscallback.length() > 0)
995     {
996       JSObject scriptObject = null; 
997       try {
998         scriptObject = JSObject.getWindow(this);
999       } catch (Exception ex) {};
1000       if (scriptObject != null)
1001       {
1002         try
1003         {
1004           // do onInit with the JS executor thread
1005           new JSFunctionExec(this).executeJavascriptFunction(true,
1006                   initjscallback, null, "Calling oninit callback '" + initjscallback
1007                   + "'.");
1008         } catch (Exception e)
1009         {
1010           System.err.println("Exception when executing _oninit callback '"
1011                   + initjscallback + "'.");
1012           e.printStackTrace();
1013         }
1014       }
1015       else
1016       {
1017         System.err.println("Not executing _oninit callback '"
1018                 + initjscallback + "' - no scripting allowed.");
1019       }
1020     }
1021   }
1022
1023   /**
1024    * Initialises and displays a new java.awt.Frame
1025    * 
1026    * @param frame
1027    *          java.awt.Frame to be displayed
1028    * @param title
1029    *          title of new frame
1030    * @param width
1031    *          width if new frame
1032    * @param height
1033    *          height of new frame
1034    */
1035   public static void addFrame(final Frame frame, String title, int width,
1036           int height)
1037   {
1038     frame.setLocation(lastFrameX, lastFrameY);
1039     lastFrameX += 40;
1040     lastFrameY += 40;
1041     frame.setSize(width, height);
1042     frame.setTitle(title);
1043     frame.addWindowListener(new WindowAdapter()
1044     {
1045       public void windowClosing(WindowEvent e)
1046       {
1047         if (frame instanceof AlignFrame)
1048         {
1049           ((AlignFrame) frame).closeMenuItem_actionPerformed();
1050           if (((AlignFrame) frame).viewport.applet.currentAlignFrame == frame)
1051           {
1052             ((AlignFrame) frame).viewport.applet.currentAlignFrame = null;
1053           }
1054         }
1055         lastFrameX -= 40;
1056         lastFrameY -= 40;
1057         if (frame instanceof EmbmenuFrame)
1058         {
1059           ((EmbmenuFrame) frame).destroyMenus();
1060         }
1061         frame.setMenuBar(null);
1062         frame.dispose();
1063       }
1064
1065       public void windowActivated(WindowEvent e)
1066       {
1067         if (frame instanceof AlignFrame)
1068         {
1069           ((AlignFrame) frame).viewport.applet.currentAlignFrame = (AlignFrame) frame;
1070           if (debug)
1071           {
1072             System.err.println("Activated window " + frame);
1073           }
1074         }
1075         // be good.
1076         super.windowActivated(e);
1077       }
1078       /*
1079        * Probably not necessary to do this - see TODO above. (non-Javadoc)
1080        * 
1081        * @see
1082        * java.awt.event.WindowAdapter#windowDeactivated(java.awt.event.WindowEvent
1083        * )
1084        * 
1085        * public void windowDeactivated(WindowEvent e) { if (currentAlignFrame ==
1086        * frame) { currentAlignFrame = null; if (debug) {
1087        * System.err.println("Deactivated window "+frame); } }
1088        * super.windowDeactivated(e); }
1089        */
1090     });
1091     frame.setVisible(true);
1092   }
1093
1094   /**
1095    * This paints the background surrounding the "Launch Jalview button" <br>
1096    * <br>
1097    * If file given in parameter not found, displays error message
1098    * 
1099    * @param g
1100    *          graphics context
1101    */
1102   public void paint(Graphics g)
1103   {
1104     if (!fileFound)
1105     {
1106       g.setColor(new Color(200, 200, 200));
1107       g.setColor(Color.cyan);
1108       g.fillRect(0, 0, getSize().width, getSize().height);
1109       g.setColor(Color.red);
1110       g.drawString("Jalview can't open file", 5, 15);
1111       g.drawString("\"" + file + "\"", 5, 30);
1112     }
1113     else if (embedded)
1114     {
1115       g.setColor(Color.black);
1116       g.setFont(new Font("Arial", Font.BOLD, 24));
1117       g.drawString("Jalview Applet", 50, getSize().height / 2 - 30);
1118       g.drawString("Loading Data...", 50, getSize().height / 2);
1119     }
1120   }
1121   /**
1122    * get all components associated with the applet of the given type
1123    * 
1124    * @param class1
1125    * @return
1126    */
1127   public Vector getAppletWindow(Class class1)
1128   {
1129     Vector wnds = new Vector();
1130     Component[] cmp = getComponents();
1131     if (cmp != null)
1132     {
1133       for (int i = 0; i < cmp.length; i++)
1134       {
1135         if (class1.isAssignableFrom(cmp[i].getClass()))
1136         {
1137           wnds.addElement(cmp);
1138         }
1139       }
1140     }
1141     return wnds;
1142   }
1143
1144   class LoadJmolThread extends Thread
1145   {
1146     private boolean running = false;
1147
1148     public void run()
1149     {
1150       if (running || checkedForJmol)
1151       {
1152         return;
1153       }
1154       running = true;
1155       if (checkForJmol)
1156       {
1157         try
1158         {
1159           if (!System.getProperty("java.version").startsWith("1.1"))
1160           {
1161             Class.forName("org.jmol.adapter.smarter.SmarterJmolAdapter");
1162             jmolAvailable = true;
1163           }
1164           if (!jmolAvailable)
1165           {
1166             System.out
1167                     .println("Jmol not available - Using MCview for structures");
1168           }
1169         } catch (java.lang.ClassNotFoundException ex)
1170         {
1171         }
1172       }
1173       else
1174       {
1175         jmolAvailable = false;
1176         if (debug)
1177         {
1178           System.err
1179                   .println("Skipping Jmol check. Will use MCView (probably)");
1180         }
1181       }
1182       checkedForJmol = true;
1183       running = false;
1184     }
1185
1186     public boolean notFinished()
1187     {
1188       return running || !checkedForJmol;
1189     }
1190   }
1191
1192   class LoadingThread extends Thread
1193   {
1194     /**
1195      * State variable: File source
1196      */
1197     String file;
1198
1199     /**
1200      * State variable: protocol for access to file source
1201      */
1202     String protocol;
1203
1204     /**
1205      * State variable: format of file source
1206      */
1207     String format;
1208
1209     String _file;
1210
1211     JalviewLite applet;
1212
1213     private void dbgMsg(String msg)
1214     {
1215       if (applet.debug)
1216       {
1217         System.err.println(msg);
1218       }
1219     }
1220
1221     /**
1222      * update the protocol state variable for accessing the datasource located
1223      * by file.
1224      * 
1225      * @param file
1226      * @return possibly updated datasource string
1227      */
1228     public String setProtocolState(String file)
1229     {
1230       if (file.startsWith("PASTE"))
1231       {
1232         file = file.substring(5);
1233         protocol = AppletFormatAdapter.PASTE;
1234       }
1235       else if (inArchive(file))
1236       {
1237         protocol = AppletFormatAdapter.CLASSLOADER;
1238       }
1239       else
1240       {
1241         file = addProtocol(file);
1242         protocol = AppletFormatAdapter.URL;
1243       }
1244       dbgMsg("Protocol identified as '" + protocol + "'");
1245       return file;
1246     }
1247
1248     public LoadingThread(String _file, JalviewLite _applet)
1249     {
1250       this._file = _file;
1251       applet = _applet;
1252     }
1253
1254     public void run()
1255     {
1256       LoadJmolThread jmolchecker = new LoadJmolThread();
1257       jmolchecker.start();
1258       while (jmolchecker.notFinished())
1259       {
1260         // wait around until the Jmol check is complete.
1261         try
1262         {
1263           Thread.sleep(2);
1264         } catch (Exception e)
1265         {
1266         }
1267         ;
1268       }
1269       startLoading();
1270       // applet.callInitCallback();
1271     }
1272
1273     private void startLoading()
1274     {
1275       AlignFrame newAlignFrame;
1276       dbgMsg("Loading thread started with:\n>>file\n" + _file + ">>endfile");
1277       file = setProtocolState(_file);
1278
1279       format = new jalview.io.IdentifyFile().Identify(file, protocol);
1280       dbgMsg("File identified as '" + format + "'");
1281       dbgMsg("Loading started.");
1282       Alignment al = null;
1283       try
1284       {
1285         al = new AppletFormatAdapter().readFile(file, protocol, format);
1286       } catch (java.io.IOException ex)
1287       {
1288         dbgMsg("File load exception.");
1289         ex.printStackTrace();
1290         if (debug)
1291         {
1292           try
1293           {
1294             FileParse fp = new FileParse(file, protocol);
1295             String ln = null;
1296             dbgMsg(">>>Dumping contents of '" + file + "' " + "("
1297                     + protocol + ")");
1298             while ((ln = fp.nextLine()) != null)
1299             {
1300               dbgMsg(ln);
1301             }
1302             dbgMsg(">>>Dump finished.");
1303           } catch (Exception e)
1304           {
1305             System.err
1306                     .println("Exception when trying to dump the content of the file parameter.");
1307             e.printStackTrace();
1308           }
1309         }
1310       }
1311       if ((al != null) && (al.getHeight() > 0))
1312       {
1313         dbgMsg("Successfully loaded file.");
1314         newAlignFrame = new AlignFrame(al, applet, file, embedded);
1315         if (initialAlignFrame == null)
1316         {
1317           initialAlignFrame = newAlignFrame;
1318         }
1319         // update the focus.
1320         currentAlignFrame = newAlignFrame;
1321
1322         if (protocol == jalview.io.AppletFormatAdapter.PASTE)
1323         {
1324           newAlignFrame.setTitle("Sequences from "
1325                   + applet.getDocumentBase());
1326         }
1327
1328         newAlignFrame.statusBar.setText("Successfully loaded file " + file);
1329
1330         String treeFile = applet.getParameter("tree");
1331         if (treeFile == null)
1332         {
1333           treeFile = applet.getParameter("treeFile");
1334         }
1335
1336         if (treeFile != null)
1337         {
1338           try
1339           {
1340             treeFile = setProtocolState(treeFile);
1341             /*
1342              * if (inArchive(treeFile)) { protocol =
1343              * AppletFormatAdapter.CLASSLOADER; } else { protocol =
1344              * AppletFormatAdapter.URL; treeFile = addProtocol(treeFile); }
1345              */
1346             jalview.io.NewickFile fin = new jalview.io.NewickFile(treeFile,
1347                     protocol);
1348
1349             fin.parse();
1350
1351             if (fin.getTree() != null)
1352             {
1353               newAlignFrame.loadTree(fin, treeFile);
1354               dbgMsg("Successfuly imported tree.");
1355             }
1356             else
1357             {
1358               dbgMsg("Tree parameter did not resolve to a valid tree.");
1359             }
1360           } catch (Exception ex)
1361           {
1362             ex.printStackTrace();
1363           }
1364         }
1365
1366         String param = applet.getParameter("features");
1367         if (param != null)
1368         {
1369           param = setProtocolState(param);
1370
1371           newAlignFrame.parseFeaturesFile(param, protocol);
1372         }
1373
1374         param = applet.getParameter("showFeatureSettings");
1375         if (param != null && param.equalsIgnoreCase("true"))
1376         {
1377           newAlignFrame.viewport.showSequenceFeatures(true);
1378           new FeatureSettings(newAlignFrame.alignPanel);
1379         }
1380
1381         param = applet.getParameter("annotations");
1382         if (param != null)
1383         {
1384           param = setProtocolState(param);
1385
1386           if (new AnnotationFile().readAnnotationFile(
1387                   newAlignFrame.viewport.getAlignment(), param, protocol))
1388           {
1389             newAlignFrame.alignPanel.fontChanged();
1390             newAlignFrame.alignPanel.setScrollValues(0, 0);
1391           }
1392           else
1393           {
1394             System.err
1395                     .println("Annotations were not added from annotation file '"
1396                             + param + "'");
1397           }
1398
1399         }
1400
1401         param = applet.getParameter("jnetfile");
1402         if (param != null)
1403         {
1404           try
1405           {
1406             param = setProtocolState(param);
1407             jalview.io.JPredFile predictions = new jalview.io.JPredFile(
1408                     param, protocol);
1409             JnetAnnotationMaker.add_annotation(predictions,
1410                     newAlignFrame.viewport.getAlignment(), 0, false); // false==do
1411             // not
1412             // add
1413             // sequence
1414             // profile
1415             // from
1416             // concise
1417             // output
1418             newAlignFrame.alignPanel.fontChanged();
1419             newAlignFrame.alignPanel.setScrollValues(0, 0);
1420           } catch (Exception ex)
1421           {
1422             ex.printStackTrace();
1423           }
1424         }
1425         /*
1426          * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6
1427          * - related to JAL-434
1428          */
1429         applet.setAlignPdbStructures(getDefaultParameter("alignpdbfiles",
1430                 false));
1431         /*
1432          * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
1433          * PDB|1GAQ|1GAQ|C">
1434          * 
1435          * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
1436          * 
1437          * <param name="PDBfile3" value="1q0o Q45135_9MICO">
1438          */
1439
1440         int pdbFileCount = 0;
1441         // Accumulate pdbs here if they are heading for the same view (if
1442         // alignPdbStructures is true)
1443         Vector pdbs = new Vector();
1444         do
1445         {
1446           if (pdbFileCount > 0)
1447             param = applet.getParameter("PDBFILE" + pdbFileCount);
1448           else
1449             param = applet.getParameter("PDBFILE");
1450
1451           if (param != null)
1452           {
1453             PDBEntry pdb = new PDBEntry();
1454
1455             String seqstring;
1456             SequenceI[] seqs = null;
1457             String[] chains = null;
1458
1459             StringTokenizer st = new StringTokenizer(param, " ");
1460
1461             if (st.countTokens() < 2)
1462             {
1463               String sequence = applet.getParameter("PDBSEQ");
1464               if (sequence != null)
1465                 seqs = new SequenceI[]
1466                 { (Sequence) newAlignFrame.getAlignViewport()
1467                         .getAlignment().findName(sequence) };
1468
1469             }
1470             else
1471             {
1472               param = st.nextToken();
1473               Vector tmp = new Vector();
1474               Vector tmp2 = new Vector();
1475
1476               while (st.hasMoreTokens())
1477               {
1478                 seqstring = st.nextToken();
1479                 StringTokenizer st2 = new StringTokenizer(seqstring, "=");
1480                 if (st2.countTokens() > 1)
1481                 {
1482                   // This is the chain
1483                   tmp2.addElement(st2.nextToken());
1484                   seqstring = st2.nextToken();
1485                 }
1486                 tmp.addElement((Sequence) newAlignFrame.getAlignViewport()
1487                         .getAlignment().findName(seqstring));
1488               }
1489
1490               seqs = new SequenceI[tmp.size()];
1491               tmp.copyInto(seqs);
1492               if (tmp2.size() == tmp.size())
1493               {
1494                 chains = new String[tmp2.size()];
1495                 tmp2.copyInto(chains);
1496               }
1497             }
1498             param = setProtocolState(param);
1499
1500             if (// !jmolAvailable
1501             // &&
1502             protocol == AppletFormatAdapter.CLASSLOADER)
1503             {
1504               // TODO: verify this Re:
1505               // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
1506               // This exception preserves the current behaviour where, even if
1507               // the local pdb file was identified in the class loader
1508               protocol = AppletFormatAdapter.URL; // this is probably NOT
1509               // CORRECT!
1510               param = addProtocol(param); //
1511             }
1512
1513             pdb.setFile(param);
1514
1515             if (seqs != null)
1516             {
1517               for (int i = 0; i < seqs.length; i++)
1518               {
1519                 if (seqs[i] != null)
1520                 {
1521                   ((Sequence) seqs[i]).addPDBId(pdb);
1522                 }
1523                 else
1524                 {
1525                   if (JalviewLite.debug)
1526                   {
1527                     // this may not really be a problem but we give a warning
1528                     // anyway
1529                     System.err
1530                             .println("Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "
1531                                     + i + ")");
1532                   }
1533                 }
1534               }
1535
1536               if (!alignPdbStructures)
1537               {
1538                 newAlignFrame.newStructureView(applet, pdb, seqs, chains,
1539                         protocol);
1540               }
1541               else
1542               {
1543                 pdbs.addElement(new Object[]
1544                 { pdb, seqs, chains, new String(protocol) });
1545               }
1546             }
1547           }
1548
1549           pdbFileCount++;
1550         } while (pdbFileCount < 10);
1551         if (pdbs.size() > 0)
1552         {
1553           SequenceI[][] seqs = new SequenceI[pdbs.size()][];
1554           PDBEntry[] pdb = new PDBEntry[pdbs.size()];
1555           String[][] chains = new String[pdbs.size()][];
1556           String[] protocols = new String[pdbs.size()];
1557           for (int pdbsi = 0, pdbsiSize = pdbs.size(); pdbsi < pdbsiSize; pdbsi++)
1558           {
1559             Object[] o = (Object[]) pdbs.elementAt(pdbsi);
1560             pdb[pdbsi] = (PDBEntry) o[0];
1561             seqs[pdbsi] = (SequenceI[]) o[1];
1562             chains[pdbsi] = (String[]) o[2];
1563             protocols[pdbsi] = (String) o[3];
1564           }
1565           newAlignFrame.alignedStructureView(applet, pdb, seqs, chains,
1566                   protocols);
1567
1568         }
1569         // ///////////////////////////
1570         // modify display of features
1571         //
1572         // hide specific groups
1573         param = applet.getParameter("hidefeaturegroups");
1574         if (param != null)
1575         {
1576           applet.setFeatureGroupStateOn(newAlignFrame, param, false);
1577         }
1578         // show specific groups
1579         param = applet.getParameter("showfeaturegroups");
1580         if (param != null)
1581         {
1582           applet.setFeatureGroupStateOn(newAlignFrame, param, true);
1583         }
1584       }
1585       else
1586       {
1587         fileFound = false;
1588         applet.remove(launcher);
1589         applet.repaint();
1590       }
1591     }
1592
1593     /**
1594      * Discovers whether the given file is in the Applet Archive
1595      * 
1596      * @param file
1597      *          String
1598      * @return boolean
1599      */
1600     boolean inArchive(String file)
1601     {
1602       // This might throw a security exception in certain browsers
1603       // Netscape Communicator for instance.
1604       try
1605       {
1606         boolean rtn = (getClass().getResourceAsStream("/" + file) != null);
1607         if (debug)
1608         {
1609           System.err.println("Resource '" + file + "' was "
1610                   + (rtn ? "" : "not") + " located by classloader.");
1611         }
1612         return rtn;
1613       } catch (Exception ex)
1614       {
1615         System.out.println("Exception checking resources: " + file + " "
1616                 + ex);
1617         return false;
1618       }
1619     }
1620
1621     String addProtocol(String file)
1622     {
1623       if (file.indexOf("://") == -1)
1624       {
1625         file = applet.getCodeBase() + file;
1626         if (debug)
1627         {
1628           System.err.println("Prepended codebase for resource: '" + file
1629                   + "'");
1630         }
1631       }
1632
1633       return file;
1634     }
1635   }
1636
1637   /**
1638    * @return the default alignFrame acted on by the public applet methods. May
1639    *         return null with an error message on System.err indicating the
1640    *         fact.
1641    */
1642   public AlignFrame getDefaultTargetFrame()
1643   {
1644     if (currentAlignFrame != null)
1645     {
1646       return currentAlignFrame;
1647     }
1648     if (initialAlignFrame != null)
1649     {
1650       return initialAlignFrame;
1651     }
1652     System.err
1653             .println("Implementation error: Jalview Applet API cannot work out which AlignFrame to use.");
1654     return null;
1655   }
1656
1657   /**
1658    * separator used for separatorList
1659    */
1660   protected String separator = ""+((char)0x00AC); // the default used to be '|' but many sequence IDS include pipes.
1661   
1662   /**
1663    * parse the string into a list
1664    * 
1665    * @param list
1666    * @return elements separated by separator
1667    */
1668   public String[] separatorListToArray(String list)
1669   {
1670     return separatorListToArray(list, separator);
1671   }
1672
1673   /**
1674    * parse the string into a list
1675    * 
1676    * @param list
1677    * @param separator
1678    * @return elements separated by separator
1679    */
1680   public String[] separatorListToArray(String list, String separator)
1681   {
1682     // note separator local variable intentionally masks object field
1683     int seplen = separator.length();
1684     if (list == null || list.equals("") || list.equals(separator))
1685       return null;
1686     java.util.Vector jv = new Vector();
1687     int cp = 0, pos;
1688     while ((pos = list.indexOf(separator, cp)) > cp)
1689     {
1690       jv.addElement(list.substring(cp, pos));
1691       cp = pos + seplen;
1692     }
1693     if (cp < list.length())
1694     {
1695       String c = list.substring(cp);
1696       if (!c.equals(separator))
1697       {
1698         jv.addElement(c);
1699       }
1700     }
1701     if (jv.size() > 0)
1702     {
1703       String[] v = new String[jv.size()];
1704       for (int i = 0; i < v.length; i++)
1705       {
1706         v[i] = (String) jv.elementAt(i);
1707       }
1708       jv.removeAllElements();
1709       if (debug)
1710       {
1711         System.err.println("Array from '" + separator
1712                 + "' separated List:\n" + v.length);
1713         for (int i = 0; i < v.length; i++)
1714         {
1715           System.err.println("item " + i + " '" + v[i] + "'");
1716         }
1717       }
1718       return v;
1719     }
1720     if (debug)
1721     {
1722       System.err.println("Empty Array from '" + separator
1723               + "' separated List");
1724     }
1725     return null;
1726   }
1727
1728   /**
1729    * concatenate the list with separator
1730    * 
1731    * @param list
1732    * @return concatenated string
1733    */
1734   public String arrayToSeparatorList(String[] list)
1735   {
1736     return arrayToSeparatorList(list, separator);
1737   }
1738
1739   /**
1740    * concatenate the list with separator
1741    * 
1742    * @param list
1743    * @param separator
1744    * @return concatenated string
1745    */
1746   public String arrayToSeparatorList(String[] list, String separator)
1747   {
1748     StringBuffer v = new StringBuffer();
1749     if (list != null && list.length > 0)
1750     {
1751       for (int i = 0, iSize = list.length; i < iSize; i++)
1752       {
1753         if (list[i] != null)
1754         {
1755           if (i > 0)
1756           {
1757             v.append(separator);
1758           }
1759           v.append(list[i]);
1760         }
1761       }
1762       if (debug)
1763       {
1764         System.err.println("Returning '" + separator
1765                 + "' separated List:\n");
1766         System.err.println(v);
1767       }
1768       return v.toString();
1769     }
1770     if (debug)
1771     {
1772       System.err.println("Returning empty '" + separator
1773               + "' separated List\n");
1774     }
1775     return "" + separator;
1776   }
1777
1778   /**
1779    * @return
1780    * @see jalview.appletgui.AlignFrame#getFeatureGroups()
1781    */
1782   public String getFeatureGroups()
1783   {
1784     String lst = arrayToSeparatorList(getDefaultTargetFrame()
1785             .getFeatureGroups());
1786     return lst;
1787   }
1788
1789   /**
1790    * @param alf
1791    *          alignframe to get feature groups on
1792    * @return
1793    * @see jalview.appletgui.AlignFrame#getFeatureGroups()
1794    */
1795   public String getFeatureGroupsOn(AlignFrame alf)
1796   {
1797     String lst = arrayToSeparatorList(alf.getFeatureGroups());
1798     return lst;
1799   }
1800
1801   /**
1802    * @param visible
1803    * @return
1804    * @see jalview.appletgui.AlignFrame#getFeatureGroupsOfState(boolean)
1805    */
1806   public String getFeatureGroupsOfState(boolean visible)
1807   {
1808     return arrayToSeparatorList(getDefaultTargetFrame()
1809             .getFeatureGroupsOfState(visible));
1810   }
1811
1812   /**
1813    * @param alf
1814    *          align frame to get groups of state visible
1815    * @param visible
1816    * @return
1817    * @see jalview.appletgui.AlignFrame#getFeatureGroupsOfState(boolean)
1818    */
1819   public String getFeatureGroupsOfStateOn(AlignFrame alf, boolean visible)
1820   {
1821     return arrayToSeparatorList(alf.getFeatureGroupsOfState(visible));
1822   }
1823
1824   /**
1825    * @param groups
1826    *          tab separated list of group names
1827    * @param state
1828    *          true or false
1829    * @see jalview.appletgui.AlignFrame#setFeatureGroupState(java.lang.String[],
1830    *      boolean)
1831    */
1832   public void setFeatureGroupStateOn(AlignFrame alf, String groups,
1833           boolean state)
1834   {
1835     boolean st = state;// !(state==null || state.equals("") ||
1836     // state.toLowerCase().equals("false"));
1837     alf.setFeatureGroupState(separatorListToArray(groups), st);
1838   }
1839
1840   public void setFeatureGroupState(String groups, boolean state)
1841   {
1842     setFeatureGroupStateOn(getDefaultTargetFrame(), groups, state);
1843   }
1844
1845   /**
1846    * List separator string
1847    * 
1848    * @return the separator
1849    */
1850   public String getSeparator()
1851   {
1852     return separator;
1853   }
1854
1855   /**
1856    * List separator string
1857    * 
1858    * @param separator
1859    *          the separator to set. empty string will reset separator to default
1860    */
1861   public void setSeparator(String separator)
1862   {
1863     if (separator==null || separator.length()<1)
1864     {
1865       // reset to default
1866       separator = ""+((char)0x00AC);
1867     }
1868     this.separator = separator;
1869     if (debug)
1870     {
1871       System.err.println("Default Separator now: '"+separator+"'");
1872     }
1873   }
1874
1875   /**
1876    * get boolean value of applet parameter 'name' and return default if
1877    * parameter is not set
1878    * 
1879    * @param name
1880    *          name of paremeter
1881    * @param def
1882    *          the value to return otherwise
1883    * @return true or false
1884    */
1885   public boolean getDefaultParameter(String name, boolean def)
1886   {
1887     String stn;
1888     if ((stn = getParameter(name)) == null)
1889     {
1890       return def;
1891     }
1892     if (stn.toLowerCase().equals("true"))
1893     {
1894       return true;
1895     }
1896     return false;
1897   }
1898
1899   /**
1900    * bind a pdb file to a sequence in the given alignFrame.
1901    * 
1902    * @param alFrame
1903    *          - null or specific alignFrame. This specifies the dataset that
1904    *          will be searched for a seuqence called sequenceId
1905    * @param sequenceId
1906    *          - sequenceId within the dataset.
1907    * @param pdbEntryString
1908    *          - the short name for the PDB file
1909    * @param pdbFile
1910    *          - pdb file - either a URL or a valid PDB file.
1911    * @return true if binding was as success TODO: consider making an exception
1912    *         structure for indicating when PDB parsing or seqeunceId location
1913    *         fails.
1914    */
1915   public boolean addPdbFile(AlignFrame alFrame, String sequenceId,
1916           String pdbEntryString, String pdbFile)
1917   {
1918     return alFrame.addPdbFile(sequenceId, pdbEntryString, pdbFile);
1919   }
1920
1921   protected void setAlignPdbStructures(boolean alignPdbStructures)
1922   {
1923     this.alignPdbStructures = alignPdbStructures;
1924   }
1925
1926   public boolean isAlignPdbStructures()
1927   {
1928     return alignPdbStructures;
1929   }
1930
1931   public void start()
1932   {
1933     callInitCallback();
1934   }
1935
1936   /**
1937    * bind structures in a viewer to any matching sequences in an alignFrame (use
1938    * sequenceIds to limit scope of search to specific sequences)
1939    * 
1940    * @param alFrame
1941    * @param viewer
1942    * @param sequenceIds
1943    * @return TODO: consider making an exception structure for indicating when
1944    *         binding fails public SequenceStructureBinding
1945    *         addStructureViewInstance( AlignFrame alFrame, Object viewer, String
1946    *         sequenceIds) {
1947    * 
1948    *         if (sequenceIds != null && sequenceIds.length() > 0) { return
1949    *         alFrame.addStructureViewInstance(viewer,
1950    *         separatorListToArray(sequenceIds)); } else { return
1951    *         alFrame.addStructureViewInstance(viewer, null); } // return null; }
1952    */
1953 }