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