minor bugfix for case when PDB sequence strings are not parsed and added new javascri...
[jalview.git] / src / jalview / bin / JalviewLite.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.bin;
20
21 import java.applet.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25 import java.util.*;
26
27 import jalview.appletgui.*;
28 import jalview.datamodel.*;
29 import jalview.io.*;
30
31 /**
32  * Jalview Applet. Runs in Java 1.18 runtime
33  *
34  * @author $author$
35  * @version $Revision$
36  */
37 public class JalviewLite
38     extends Applet
39 {
40
41
42
43   ///////////////////////////////////////////
44   //The following public methods maybe called
45   //externally, eg via javascript in HTML page
46   /**
47    * @return String list of selected sequence IDs, each terminated by "¬" (¬)
48    */
49   public String getSelectedSequences()
50   {
51     return getSelectedSequencesFrom(getDefaultTargetFrame());
52   }
53   /**
54    * @param sep separator string or null for default
55    * @return String list of selected sequence IDs, each terminated by sep or ("¬" as default)
56    */
57   public String getSelectedSequences(String sep)
58   {
59     return getSelectedSequencesFrom(getDefaultTargetFrame(), sep);
60   }
61   /**
62    * @param alf alignframe containing selection
63    * @return String list of selected sequence IDs, each terminated by "¬"
64    *  
65    */
66   public String getSelectedSequencesFrom(AlignFrame alf)
67   {
68     return getSelectedSequencesFrom(alf, "¬");
69   }
70   /**
71    * get list of selected sequence IDs separated by given separator 
72    * @param alf window containing selection 
73    * @param sep separator string to use - default is "¬"
74    * @return String list of selected sequence IDs, each terminated by the given separator
75    */
76   public String getSelectedSequencesFrom(AlignFrame alf, String sep)
77   {
78     StringBuffer result = new StringBuffer("");
79     if (sep==null || sep.length()==0)
80     {
81       sep = "¬";
82     }
83     if (alf.viewport.getSelectionGroup() != null)
84     {
85       SequenceI[] seqs = alf.viewport.getSelectionGroup().
86           getSequencesInOrder(
87                   alf.viewport.getAlignment());
88
89       for (int i = 0; i < seqs.length; i++)
90       {
91         result.append(seqs[i].getName());
92         result.append(sep); 
93       }
94     }
95
96     return result.toString();
97   }
98
99   /**
100    * get sequences selected in current alignFrame and return their alignment in format 'format' either with or without suffix 
101    * @param alf - where selection is
102    * @param format - format of alignment file
103    * @param suffix - "true" to append /start-end string to each sequence ID 
104    * @return selected sequences as flat file or empty string if there was no current selection
105    */
106   public String getSelectedSequencesAsAlignment(String format, String suffix) {
107     return getSelectedSequencesAsAlignmentFrom(currentAlignFrame, format, suffix);
108   }
109
110   /**
111    * get sequences selected in alf and return their alignment in format 'format' either with or without suffix 
112    * @param alf - where selection is
113    * @param format - format of alignment file
114    * @param suffix - "true" to append /start-end string to each sequence ID 
115    * @return selected sequences as flat file or empty string if there was no current selection
116    */
117   public String getSelectedSequencesAsAlignmentFrom(AlignFrame alf, String format, String suffix)
118   {
119     try
120     {
121       boolean seqlimits = suffix.equalsIgnoreCase("true");
122       if (alf.viewport.getSelectionGroup()!=null)
123       {
124         String reply = new AppletFormatAdapter().formatSequences(format,
125                 new Alignment(alf.viewport.getSelectionAsNewSequence()), seqlimits);
126         return reply;
127       }
128     }
129     catch (Exception ex)
130     {
131       ex.printStackTrace();
132       return "Error retrieving alignment in " + format + " format. ";
133     }
134     return "";
135   }
136
137   public String getAlignment(String format)
138   {
139     return getAlignmentFrom(getDefaultTargetFrame(), format, "true");
140   }
141   public String getAlignmentFrom(AlignFrame alf, String format)
142   {
143     return getAlignmentFrom(alf, format, "true");
144   }
145   public String getAlignment(String format, String suffix)
146   {
147     return getAlignmentFrom(getDefaultTargetFrame(), format, suffix);
148   }
149   public String getAlignmentFrom(AlignFrame alf, String format, String suffix)
150   {
151     try
152     {
153       boolean seqlimits = suffix.equalsIgnoreCase("true");
154
155       String reply = new AppletFormatAdapter().formatSequences(format,
156           alf.viewport.getAlignment(), seqlimits);
157       return reply;
158     }
159     catch (Exception ex)
160     {
161       ex.printStackTrace();
162       return "Error retrieving alignment in " + format + " format. ";
163     }
164   }
165
166   public void loadAnnotation(String annotation)
167   {
168     loadAnnotationFrom(getDefaultTargetFrame(), annotation);
169   }
170   public void loadAnnotationFrom(AlignFrame alf, String annotation)
171   {
172     if (new AnnotationFile().readAnnotationFile(
173         alf.getAlignViewport().getAlignment(), annotation,
174         AppletFormatAdapter.PASTE))
175     {
176       alf.alignPanel.fontChanged();
177       alf.alignPanel.setScrollValues(0, 0);
178     }
179     else
180     {
181       alf.parseFeaturesFile(annotation, AppletFormatAdapter.PASTE);
182     }
183   }
184
185   public String getFeatures(String format)
186   {
187     return getFeaturesFrom(getDefaultTargetFrame(), format);
188   }
189   public String getFeaturesFrom(AlignFrame alf, String format)
190   {
191     return alf.outputFeatures(false, format);
192   }
193   public String getAnnotation()
194   {
195     return getAnnotationFrom(getDefaultTargetFrame());
196   }
197   public String getAnnotationFrom(AlignFrame alf)
198   {
199     return alf.outputAnnotations(false);
200   }
201   public AlignFrame newView()
202   {
203     return newViewFrom(getDefaultTargetFrame());
204   }
205   public AlignFrame newView(String name)
206   {
207     return newViewFrom(getDefaultTargetFrame(), name);
208   }
209
210   public AlignFrame newViewFrom(AlignFrame alf)
211   {
212     return alf.newView(null);
213   }
214   public AlignFrame newViewFrom(AlignFrame alf, String name)
215   {
216     return alf.newView(name);
217   }
218   /**
219    * 
220    * @param text alignment file as a string
221    * @param title window title 
222    * @return null or new alignment frame
223    */
224   public AlignFrame loadAlignment(String text, String title)
225   {
226     Alignment al = null;
227     String format = new IdentifyFile().Identify(text, AppletFormatAdapter.PASTE);
228     try
229     {
230       al = new AppletFormatAdapter().readFile(text,
231                                               AppletFormatAdapter.PASTE,
232                                               format);
233       if (al.getHeight() > 0)
234       {
235         return new AlignFrame(al, this, title, false);
236       }
237     }
238     catch (java.io.IOException ex)
239     {
240       ex.printStackTrace();
241     }
242     return null;
243   }
244
245   ////////////////////////////////////////////////
246   ////////////////////////////////////////////////
247
248
249
250   static int lastFrameX = 200;
251   static int lastFrameY = 200;
252   boolean fileFound = true;
253   String file = "No file";
254   Button launcher = new Button("Start Jalview");
255
256   /**
257    * The currentAlignFrame is static, it will change
258    * if and when the user selects a new window.
259    * Note that it will *never* point back to the embedded AlignFrame 
260    * if the applet is started as embedded on the page and then afterwards a new view is created.
261    */
262   public static AlignFrame currentAlignFrame;
263
264   /** 
265    * This is the first frame to be displayed, and does not change.
266    * API calls will default to this instance if currentAlignFrame is null.
267    */
268   AlignFrame initialAlignFrame;
269
270   boolean embedded = false;
271
272   public boolean jmolAvailable = false;
273   public static boolean debug;
274
275   /**
276    * init method for Jalview Applet
277    */
278   public void init()
279   {
280     String dbg = getParameter("debug");
281     if (dbg!=null)
282     {
283       debug = dbg.toLowerCase().equals("true");
284     }
285     /**
286      * get the separator parameter if present
287      */
288     String sep = getParameter("separator");
289     if (sep!=null)
290     {
291       if (sep.length()>0)
292       {      separator = sep;
293         if (debug)
294         {
295           System.err.println("Separator set to '"+separator+"'");
296         }
297       } else {
298         throw new Error("Invalid separator parameter - must be non-zero length");
299       }
300     }
301     int r = 255;
302     int g = 255;
303     int b = 255;
304     String param = getParameter("RGB");
305
306     if (param != null)
307     {
308       try
309       {
310         r = Integer.parseInt(param.substring(0, 2), 16);
311         g = Integer.parseInt(param.substring(2, 4), 16);
312         b = Integer.parseInt(param.substring(4, 6), 16);
313       }
314       catch (Exception ex)
315       {
316         r = 255;
317         g = 255;
318         b = 255;
319       }
320     }
321
322     param = getParameter("label");
323     if (param != null)
324     {
325       launcher.setLabel(param);
326     }
327
328     this.setBackground(new Color(r, g, b));
329
330     file = getParameter("file");
331
332     if (file == null)
333     {
334       //Maybe the sequences are added as parameters
335       StringBuffer data = new StringBuffer("PASTE");
336       int i = 1;
337       while ( (file = getParameter("sequence" + i)) != null)
338       {
339         data.append(file.toString() + "\n");
340         i++;
341       }
342       if (data.length() > 5)
343       {
344         file = data.toString();
345       }
346     }
347
348     LoadJmolThread jmolAvailable = new LoadJmolThread();
349     jmolAvailable.start();
350
351     final JalviewLite applet = this;
352     if (getParameter("embedded") != null
353         && getParameter("embedded").equalsIgnoreCase("true"))
354     {
355       embedded = true;
356       LoadingThread loader = new LoadingThread(file, applet);
357       loader.start();
358     }
359     else if (file != null)
360     {
361       add(launcher);
362
363       launcher.addActionListener(new java.awt.event.ActionListener()
364       {
365         public void actionPerformed(ActionEvent e)
366         {
367           LoadingThread loader = new LoadingThread(file,
368               applet);
369           loader.start();
370         }
371       });
372     }
373     else
374     {
375       file = "NO FILE";
376       fileFound = false;
377     }
378   }
379
380
381   /**
382    * Initialises and displays a new java.awt.Frame
383    *
384    * @param frame java.awt.Frame to be displayed
385    * @param title title of new frame
386    * @param width width if new frame
387    * @param height height of new frame
388    */
389   public static void addFrame(final Frame frame, String title, int width,
390                               int height)
391   {
392     frame.setLocation(lastFrameX, lastFrameY);
393     lastFrameX += 40;
394     lastFrameY += 40;
395     frame.setSize(width, height);
396     frame.setTitle(title);
397     frame.addWindowListener(new WindowAdapter()
398     {
399       public void windowClosing(WindowEvent e)
400       {
401         if (frame instanceof AlignFrame)
402         {
403           ( (AlignFrame) frame).closeMenuItem_actionPerformed();
404         }
405         if (currentAlignFrame == frame)
406         {
407           currentAlignFrame = null;
408         }
409         lastFrameX -= 40;
410         lastFrameY -= 40;
411         if (frame instanceof EmbmenuFrame)
412         {
413           ((EmbmenuFrame) frame).destroyMenus();
414         }
415         frame.setMenuBar(null);
416         frame.dispose();
417       }
418       
419       public void windowActivated(WindowEvent e)
420       {
421         if (frame instanceof AlignFrame)
422         {
423           currentAlignFrame = (AlignFrame) frame;
424           if (debug)
425           {
426             System.err.println("Activated window "+frame);
427           }
428         }
429         // be good.
430         super.windowActivated(e);
431       }
432       /* Probably not necessary to do this - see TODO above.
433        * (non-Javadoc)
434        * @see java.awt.event.WindowAdapter#windowDeactivated(java.awt.event.WindowEvent)
435        *
436       public void windowDeactivated(WindowEvent e)
437       {
438         if (currentAlignFrame == frame)
439         {
440           currentAlignFrame = null;
441           if (debug)
442           {
443             System.err.println("Deactivated window "+frame);
444           }
445         }
446         super.windowDeactivated(e);
447       }
448        */
449     });
450     frame.setVisible(true);
451   }
452
453   /**
454    * This paints the background surrounding the "Launch Jalview button"
455    * <br>
456    * <br>If file given in parameter not found, displays error message
457    *
458    * @param g graphics context
459    */
460   public void paint(Graphics g)
461   {
462     if (!fileFound)
463     {
464       g.setColor(new Color(200, 200, 200));
465       g.setColor(Color.cyan);
466       g.fillRect(0, 0, getSize().width, getSize().height);
467       g.setColor(Color.red);
468       g.drawString("Jalview can't open file", 5, 15);
469       g.drawString("\"" + file + "\"", 5, 30);
470     }
471     else if (embedded)
472     {
473       g.setColor(Color.black);
474       g.setFont(new Font("Arial", Font.BOLD, 24));
475       g.drawString("Jalview Applet", 50, this.getSize().height / 2 - 30);
476       g.drawString("Loading Data...", 50, this.getSize().height / 2);
477     }
478   }
479
480
481   class LoadJmolThread extends Thread
482   {
483     public void run()
484     {
485       try
486       {
487         if (!System.getProperty("java.version").startsWith("1.1"))
488         {
489           Class.forName("org.jmol.adapter.smarter.SmarterJmolAdapter");
490           jmolAvailable = true;
491         }
492       }
493       catch (java.lang.ClassNotFoundException ex)
494       {
495         System.out.println("Jmol not available - Using MCview for structures");
496       }
497     }
498   }
499
500
501   class LoadingThread
502       extends Thread
503   {
504     /**
505      * State variable: File source 
506      */
507     String file; 
508     /**
509      * State variable: protocol for access to file source
510      */
511     String protocol;
512     /**
513      * State variable: format of file source
514      */
515     String format;
516     JalviewLite applet;
517     private void dbgMsg(String msg)
518     {
519       if (applet.debug)
520       {
521         System.err.println(msg);
522       }
523     }
524     /**
525      * update the protocol state variable for accessing the datasource
526      * located by file. 
527      * @param file
528      * @return possibly updated datasource string
529      */
530     public String setProtocolState(String file)
531     {
532       if (file.startsWith("PASTE"))
533       {
534         file = file.substring(5);
535         protocol = AppletFormatAdapter.PASTE;
536       }
537       else if (inArchive(file))
538       {
539         protocol = AppletFormatAdapter.CLASSLOADER;
540       }
541       else
542       {
543         file = addProtocol(file);
544         protocol = AppletFormatAdapter.URL;
545       }
546       dbgMsg("Protocol identified as '"+protocol+"'");
547       return file;
548     }
549     public LoadingThread(String _file,
550                          JalviewLite _applet)
551     {
552       dbgMsg("Loading thread started with:\n>>file\n"+_file+">>endfile");
553       file = setProtocolState(_file);
554       
555       format = new jalview.io.IdentifyFile().Identify(file, protocol);
556       dbgMsg("File identified as '"+format+"'");
557       applet = _applet;
558     }
559
560     public void run()
561     {
562       startLoading();
563     }
564
565     private void startLoading()
566     {
567       dbgMsg("Loading started.");
568       Alignment al = null;
569       try
570       {
571         al = new AppletFormatAdapter().readFile(file, protocol,
572                                                 format);
573       }
574       catch (java.io.IOException ex)
575       {
576         dbgMsg("File load exception.");
577         ex.printStackTrace();
578       }
579       if ( (al != null) && (al.getHeight() > 0))
580       {
581         dbgMsg("Successfully loaded file.");
582         initialAlignFrame =  new AlignFrame(al,
583                                            applet,
584                                            file,
585                                            embedded);
586         // update the focus.
587         currentAlignFrame = initialAlignFrame;
588
589         if (protocol == jalview.io.AppletFormatAdapter.PASTE)
590         {
591           currentAlignFrame.setTitle("Sequences from " + getDocumentBase());
592         }
593
594         currentAlignFrame.statusBar.setText("Successfully loaded file " + file);
595
596         String treeFile = applet.getParameter("tree");
597         if (treeFile == null)
598         {
599           treeFile = applet.getParameter("treeFile");
600         }
601
602         if (treeFile != null)
603         {
604           try
605           {
606             treeFile = setProtocolState(treeFile);
607             /*if (inArchive(treeFile))
608             {
609               protocol = AppletFormatAdapter.CLASSLOADER;
610             }
611             else
612             {
613               protocol = AppletFormatAdapter.URL;
614               treeFile = addProtocol(treeFile);
615             }
616              */
617             jalview.io.NewickFile fin = new jalview.io.NewickFile(treeFile,
618                 protocol);
619
620             fin.parse();
621
622             if (fin.getTree() != null)
623             {
624               currentAlignFrame.loadTree(fin, treeFile);
625               dbgMsg("Successfuly imported tree.");
626             } else {
627               dbgMsg("Tree parameter did not resolve to a valid tree.");
628             }
629           }
630           catch (Exception ex)
631           {
632             ex.printStackTrace();
633           }
634         }
635
636         String param = getParameter("features");
637         if (param != null)
638         {
639           param = setProtocolState(param);
640           
641           currentAlignFrame.parseFeaturesFile(param, protocol);
642         }
643
644         param = getParameter("showFeatureSettings");
645         if (param != null && param.equalsIgnoreCase("true"))
646         {
647           currentAlignFrame.viewport.showSequenceFeatures(true);
648           new FeatureSettings(currentAlignFrame.alignPanel);
649         }
650
651         param = getParameter("annotations");
652         if (param != null)
653         { 
654           param = setProtocolState(param);
655           
656           if (new AnnotationFile().readAnnotationFile(
657               currentAlignFrame.viewport.getAlignment(),
658               param,
659               protocol))
660           {
661             currentAlignFrame.alignPanel.fontChanged();
662             currentAlignFrame.alignPanel.setScrollValues(0, 0);
663           } else {
664             System.err.println("Annotations were not added from annotation file '"+param+"'");
665           }
666
667         }
668
669         param = getParameter("jnetfile");
670         if (param != null)
671         {
672           try
673           {
674             param = setProtocolState(param);
675             jalview.io.JPredFile predictions = new jalview.io.JPredFile(
676                 param, protocol);
677             JnetAnnotationMaker.add_annotation(predictions,
678                 currentAlignFrame.viewport.getAlignment(),
679                 0, false); // false==do not add sequence profile from concise output
680             currentAlignFrame.alignPanel.fontChanged();
681             currentAlignFrame.alignPanel.setScrollValues(0, 0);
682           }
683           catch (Exception ex)
684           {
685             ex.printStackTrace();
686           }
687         }
688
689         /*
690          <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B PDB|1GAQ|1GAQ|C">
691
692          <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
693
694          <param name="PDBfile3" value="1q0o Q45135_9MICO">
695         */
696
697
698         int pdbFileCount = 0;
699         do{
700           if (pdbFileCount > 0)
701             param = getParameter("PDBFILE" + pdbFileCount);
702           else
703             param = getParameter("PDBFILE");
704
705           if (param != null)
706           {
707             PDBEntry pdb = new PDBEntry();
708
709             String seqstring;
710             SequenceI[] seqs = null;
711             String [] chains = null;
712
713             StringTokenizer st = new StringTokenizer(param, " ");
714
715             if (st.countTokens() < 2)
716             {
717               String sequence = applet.getParameter("PDBSEQ");
718               if (sequence != null)
719                 seqs = new SequenceI[]
720                     {
721                     (Sequence) currentAlignFrame.
722                     getAlignViewport().getAlignment().
723                     findName(sequence)};
724
725             }
726             else
727             {
728               param = st.nextToken();
729               Vector tmp = new Vector();
730               Vector tmp2 = new Vector();
731
732               while (st.hasMoreTokens())
733               {
734                 seqstring = st.nextToken();
735                 StringTokenizer st2 = new StringTokenizer(seqstring,"=");
736                 if(st2.countTokens()>1)
737                 {
738                   //This is the chain
739                   tmp2.addElement(st2.nextToken());
740                   seqstring = st2.nextToken();
741                 }
742                 tmp.addElement( (Sequence) currentAlignFrame.
743                                  getAlignViewport().getAlignment().
744                                  findName(seqstring));
745               }
746
747               seqs = new SequenceI[tmp.size()];
748               tmp.copyInto(seqs);
749               if(tmp2.size()==tmp.size())
750               {
751                 chains = new String[tmp2.size()];
752                 tmp2.copyInto(chains);
753               }
754             }
755             param = setProtocolState(param);
756             
757             if (!jmolAvailable && protocol==AppletFormatAdapter.CLASSLOADER)
758             {
759               // TODO: pass PDB file in classloader on to Jmol
760               // This exception preserves the current behaviour where, even if the local pdb file was identified in the class loader
761               protocol = AppletFormatAdapter.URL; // this is probably NOT CORRECT!
762               param = addProtocol(param); // 
763             }
764
765             pdb.setFile(param);
766
767             if(seqs!=null)
768             {
769               for (int i = 0; i < seqs.length; i++)
770               {
771                 if (seqs[i]!=null)
772                 {
773                   ( (Sequence) seqs[i]).addPDBId(pdb);
774                 } else {
775                   if (JalviewLite.debug)
776                   {
777                     // this may not really be a problem but we give a warning anyway
778                     System.err.println("Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "+i+")");
779                   }
780                 }
781               }
782
783               if (jmolAvailable)
784               {
785                 new jalview.appletgui.AppletJmol(pdb,
786                                                  seqs,
787                                                  chains,
788                                                  currentAlignFrame.alignPanel,
789                                                  protocol);
790                 lastFrameX += 40;
791                 lastFrameY+=40;
792               }
793               else
794                     new MCview.AppletPDBViewer(pdb,
795                                            seqs,
796                                            chains,
797                                            currentAlignFrame.alignPanel,
798                                            protocol);
799             }
800           }
801
802           pdbFileCount++;
803         }
804         while(pdbFileCount < 10);
805         
806         /////////////////////////////
807         // modify display of features
808         //
809         // hide specific groups
810         param = getParameter("hidefeaturegroups");
811         if (param != null)
812         {
813           applet.setFeatureGroupState(param, false);
814         }
815         // show specific groups
816         param = getParameter("showfeaturegroups");
817         if (param != null)
818         {
819           applet.setFeatureGroupState(param, true);
820         }
821       }
822       else
823       {
824         fileFound = false;
825         remove(launcher);
826         repaint();
827       }
828     }
829
830     /**
831      * Discovers whether the given file is in the Applet Archive
832      * @param file String
833      * @return boolean
834      */
835     boolean inArchive(String file)
836     {
837       //This might throw a security exception in certain browsers
838       //Netscape Communicator for instance.
839       try
840       {
841         boolean rtn = (getClass().getResourceAsStream("/" + file) != null);
842         if (debug)
843         {  System.err.println("Resource '"+file+"' was "+(rtn ? "" : "not") +" located by classloader.");
844         }
845         return rtn;
846       }
847       catch (Exception ex)
848       {
849         System.out.println("Exception checking resources: " + file + " " + ex);
850         return false;
851       }
852     }
853
854     String addProtocol(String file)
855     {
856       if (file.indexOf("://") == -1)
857       {
858         file = getCodeBase() + file;
859         if (debug)
860         {
861           System.err.println("Prepended codebase for resource: '"+file+"'");
862         }
863       }
864
865       return file;
866     }
867   }
868   /**
869    * @return the default alignFrame acted on by the public applet methods.
870    * May return null with an error message on System.err indicating the fact. 
871    */
872   protected AlignFrame getDefaultTargetFrame()
873   {
874     if (currentAlignFrame!=null)
875     {
876       return currentAlignFrame;
877     }
878     if (initialAlignFrame!=null)
879     {
880       return initialAlignFrame;
881     }
882     System.err.println("Implementation error: Jalview Applet API cannot work out which AlignFrame to use.");
883     return null;
884   }
885   /**
886    * separator used for separatorList
887    */
888   protected String separator = "|"; // this is a safe(ish) separator - tabs don't work for firefox
889   /**
890    * parse the string into a list
891    * @param list
892    * @return elements separated by separator
893    */
894   public String[] separatorListToArray(String list)
895   {
896     int seplen = separator.length();
897     if (list==null || list.equals(""))
898       return null;
899     java.util.Vector jv = new Vector();
900     int cp=0,pos;
901     while ((pos=list.indexOf(separator,cp))>cp)
902     {
903       jv.addElement(list.substring(cp,pos));
904       cp = pos+seplen;
905     }
906     if (cp<list.length())
907     {
908       jv.addElement(list.substring(cp));
909     }
910     if (jv.size()>0)
911     { String[] v = new String[jv.size()];
912       for (int i=0; i<v.length; i++)
913       {
914         v[i] = (String) jv.elementAt(i);
915       }
916       jv.removeAllElements();
917       if (debug)
918       {
919         System.err.println("Array from '"+separator+"' separated List:\n"+v.length);
920         for (int i=0; i<v.length;i++)
921         {
922           System.err.println("item "+i+" '"+v[i]+"'");
923         }
924       }
925       return v;
926     }
927     if (debug)
928     {
929       System.err.println("Empty Array from '"+separator+"' separated List");
930     }
931     return null;
932   }
933   /**
934    * concatenate the list with separator
935    * @param list
936    * @return concatenated string
937    */
938   public String arrayToSeparatorList(String[] list)
939   {
940     StringBuffer v = new StringBuffer();
941     if (list!=null)
942     {
943       for (int i=0,iSize=list.length-1;i<iSize;i++)
944       { 
945         if (list[i]!=null)
946         {  
947           v.append(list[i]); 
948         }
949         v.append(separator);
950       }
951       if (list[list.length-1]!=null)
952       { v.append(list[list.length-1]);
953       }
954       if (debug)
955       {
956         System.err.println("Returning '"+separator+"' separated List:\n");
957         System.err.println(v);
958       }
959       return v.toString();
960     }
961     if (debug)
962     {
963       System.err.println("Returning empty '"+separator+"' separated List\n");
964     }
965     return "";
966   }
967   /**
968    * @return
969    * @see jalview.appletgui.AlignFrame#getFeatureGroups()
970    */
971   public String getFeatureGroups()
972   {
973     String lst = arrayToSeparatorList(getDefaultTargetFrame().getFeatureGroups());
974     return lst;
975   }
976   /**
977    * @param alf alignframe to get feature groups on
978    * @return
979    * @see jalview.appletgui.AlignFrame#getFeatureGroups()
980    */
981   public String getFeatureGroupsOn(AlignFrame alf)
982   {
983     String lst = arrayToSeparatorList(alf.getFeatureGroups());
984     return lst;
985   }
986   /**
987    * @param visible
988    * @return
989    * @see jalview.appletgui.AlignFrame#getFeatureGroupsOfState(boolean)
990    */
991   public String getFeatureGroupsOfState(boolean visible)
992   {
993     return arrayToSeparatorList(getDefaultTargetFrame().getFeatureGroupsOfState(visible));
994   }
995   /**
996    * @param alf align frame to get groups of state visible
997    * @param visible
998    * @return
999    * @see jalview.appletgui.AlignFrame#getFeatureGroupsOfState(boolean)
1000    */
1001   public String getFeatureGroupsOfStateOn(AlignFrame alf, boolean visible)
1002   {
1003     return arrayToSeparatorList(alf.getFeatureGroupsOfState(visible));
1004   }  /**
1005    * @param groups tab separated list of group names 
1006    * @param state true or false
1007    * @see jalview.appletgui.AlignFrame#setFeatureGroupState(java.lang.String[], boolean)
1008    */
1009   public void setFeatureGroupStateOn(AlignFrame alf, String groups, boolean state)
1010   {
1011     boolean st = state;//!(state==null || state.equals("") || state.toLowerCase().equals("false"));
1012     alf.setFeatureGroupState(separatorListToArray(groups), st);
1013   }
1014   public void setFeatureGroupState(String groups, boolean state)
1015   {
1016     setFeatureGroupStateOn(getDefaultTargetFrame(), groups, state);
1017   }
1018   /**
1019    * List separator string
1020    * @return the separator
1021    */
1022   public String getSeparator()
1023   {
1024     return separator;
1025   }
1026   /**
1027    * List separator string
1028    * @param separator the separator to set
1029    */
1030   public void setSeparator(String separator)
1031   {
1032     this.separator = separator;
1033   }
1034 }