0df360ebf0e2fa96881a2902a874cf8eba249739
[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       // Launch as embedded applet in page
356       embedded = true;
357       LoadingThread loader = new LoadingThread(file, applet);
358       loader.start();
359     }
360     else if (file != null)
361     {
362       if (getParameter("showbutton")==null || !getParameter("showbutton").equalsIgnoreCase("false"))
363       {
364         // Add the JalviewLite 'Button' to the page
365         add(launcher);
366         launcher.addActionListener(new java.awt.event.ActionListener()
367         {
368           public void actionPerformed(ActionEvent e)
369           {
370             LoadingThread loader = new LoadingThread(file,
371               applet);
372             loader.start();
373           }
374         });
375         } else {
376           // Open jalviewLite immediately.
377           LoadingThread loader = new LoadingThread(file, applet);
378           loader.start();
379         }
380     }
381     else
382     {
383       // jalview initialisation with no alignment. loadAlignment() method can still be called to open new alignments.
384       file = "NO FILE";
385       fileFound = false;
386     }
387   }
388
389
390   /**
391    * Initialises and displays a new java.awt.Frame
392    *
393    * @param frame java.awt.Frame to be displayed
394    * @param title title of new frame
395    * @param width width if new frame
396    * @param height height of new frame
397    */
398   public static void addFrame(final Frame frame, String title, int width,
399                               int height)
400   {
401     frame.setLocation(lastFrameX, lastFrameY);
402     lastFrameX += 40;
403     lastFrameY += 40;
404     frame.setSize(width, height);
405     frame.setTitle(title);
406     frame.addWindowListener(new WindowAdapter()
407     {
408       public void windowClosing(WindowEvent e)
409       {
410         if (frame instanceof AlignFrame)
411         {
412           ( (AlignFrame) frame).closeMenuItem_actionPerformed();
413         }
414         if (currentAlignFrame == frame)
415         {
416           currentAlignFrame = null;
417         }
418         lastFrameX -= 40;
419         lastFrameY -= 40;
420         if (frame instanceof EmbmenuFrame)
421         {
422           ((EmbmenuFrame) frame).destroyMenus();
423         }
424         frame.setMenuBar(null);
425         frame.dispose();
426       }
427       
428       public void windowActivated(WindowEvent e)
429       {
430         if (frame instanceof AlignFrame)
431         {
432           currentAlignFrame = (AlignFrame) frame;
433           if (debug)
434           {
435             System.err.println("Activated window "+frame);
436           }
437         }
438         // be good.
439         super.windowActivated(e);
440       }
441       /* Probably not necessary to do this - see TODO above.
442        * (non-Javadoc)
443        * @see java.awt.event.WindowAdapter#windowDeactivated(java.awt.event.WindowEvent)
444        *
445       public void windowDeactivated(WindowEvent e)
446       {
447         if (currentAlignFrame == frame)
448         {
449           currentAlignFrame = null;
450           if (debug)
451           {
452             System.err.println("Deactivated window "+frame);
453           }
454         }
455         super.windowDeactivated(e);
456       }
457        */
458     });
459     frame.setVisible(true);
460   }
461
462   /**
463    * This paints the background surrounding the "Launch Jalview button"
464    * <br>
465    * <br>If file given in parameter not found, displays error message
466    *
467    * @param g graphics context
468    */
469   public void paint(Graphics g)
470   {
471     if (!fileFound)
472     {
473       g.setColor(new Color(200, 200, 200));
474       g.setColor(Color.cyan);
475       g.fillRect(0, 0, getSize().width, getSize().height);
476       g.setColor(Color.red);
477       g.drawString("Jalview can't open file", 5, 15);
478       g.drawString("\"" + file + "\"", 5, 30);
479     }
480     else if (embedded)
481     {
482       g.setColor(Color.black);
483       g.setFont(new Font("Arial", Font.BOLD, 24));
484       g.drawString("Jalview Applet", 50, this.getSize().height / 2 - 30);
485       g.drawString("Loading Data...", 50, this.getSize().height / 2);
486     }
487   }
488
489
490   class LoadJmolThread extends Thread
491   {
492     public void run()
493     {
494       try
495       {
496         if (!System.getProperty("java.version").startsWith("1.1"))
497         {
498           Class.forName("org.jmol.adapter.smarter.SmarterJmolAdapter");
499           jmolAvailable = true;
500         }
501       }
502       catch (java.lang.ClassNotFoundException ex)
503       {
504         System.out.println("Jmol not available - Using MCview for structures");
505       }
506     }
507   }
508
509
510   class LoadingThread
511       extends Thread
512   {
513     /**
514      * State variable: File source 
515      */
516     String file; 
517     /**
518      * State variable: protocol for access to file source
519      */
520     String protocol;
521     /**
522      * State variable: format of file source
523      */
524     String format;
525     JalviewLite applet;
526     private void dbgMsg(String msg)
527     {
528       if (applet.debug)
529       {
530         System.err.println(msg);
531       }
532     }
533     /**
534      * update the protocol state variable for accessing the datasource
535      * located by file. 
536      * @param file
537      * @return possibly updated datasource string
538      */
539     public String setProtocolState(String file)
540     {
541       if (file.startsWith("PASTE"))
542       {
543         file = file.substring(5);
544         protocol = AppletFormatAdapter.PASTE;
545       }
546       else if (inArchive(file))
547       {
548         protocol = AppletFormatAdapter.CLASSLOADER;
549       }
550       else
551       {
552         file = addProtocol(file);
553         protocol = AppletFormatAdapter.URL;
554       }
555       dbgMsg("Protocol identified as '"+protocol+"'");
556       return file;
557     }
558     public LoadingThread(String _file,
559                          JalviewLite _applet)
560     {
561       dbgMsg("Loading thread started with:\n>>file\n"+_file+">>endfile");
562       file = setProtocolState(_file);
563       
564       format = new jalview.io.IdentifyFile().Identify(file, protocol);
565       dbgMsg("File identified as '"+format+"'");
566       applet = _applet;
567     }
568
569     public void run()
570     {
571       startLoading();
572     }
573
574     private void startLoading()
575     {
576       dbgMsg("Loading started.");
577       Alignment al = null;
578       try
579       {
580         al = new AppletFormatAdapter().readFile(file, protocol,
581                                                 format);
582       }
583       catch (java.io.IOException ex)
584       {
585         dbgMsg("File load exception.");
586         ex.printStackTrace();
587       }
588       if ( (al != null) && (al.getHeight() > 0))
589       {
590         dbgMsg("Successfully loaded file.");
591         initialAlignFrame =  new AlignFrame(al,
592                                            applet,
593                                            file,
594                                            embedded);
595         // update the focus.
596         currentAlignFrame = initialAlignFrame;
597
598         if (protocol == jalview.io.AppletFormatAdapter.PASTE)
599         {
600           currentAlignFrame.setTitle("Sequences from " + getDocumentBase());
601         }
602
603         currentAlignFrame.statusBar.setText("Successfully loaded file " + file);
604
605         String treeFile = applet.getParameter("tree");
606         if (treeFile == null)
607         {
608           treeFile = applet.getParameter("treeFile");
609         }
610
611         if (treeFile != null)
612         {
613           try
614           {
615             treeFile = setProtocolState(treeFile);
616             /*if (inArchive(treeFile))
617             {
618               protocol = AppletFormatAdapter.CLASSLOADER;
619             }
620             else
621             {
622               protocol = AppletFormatAdapter.URL;
623               treeFile = addProtocol(treeFile);
624             }
625              */
626             jalview.io.NewickFile fin = new jalview.io.NewickFile(treeFile,
627                 protocol);
628
629             fin.parse();
630
631             if (fin.getTree() != null)
632             {
633               currentAlignFrame.loadTree(fin, treeFile);
634               dbgMsg("Successfuly imported tree.");
635             } else {
636               dbgMsg("Tree parameter did not resolve to a valid tree.");
637             }
638           }
639           catch (Exception ex)
640           {
641             ex.printStackTrace();
642           }
643         }
644
645         String param = getParameter("features");
646         if (param != null)
647         {
648           param = setProtocolState(param);
649           
650           currentAlignFrame.parseFeaturesFile(param, protocol);
651         }
652
653         param = getParameter("showFeatureSettings");
654         if (param != null && param.equalsIgnoreCase("true"))
655         {
656           currentAlignFrame.viewport.showSequenceFeatures(true);
657           new FeatureSettings(currentAlignFrame.alignPanel);
658         }
659
660         param = getParameter("annotations");
661         if (param != null)
662         { 
663           param = setProtocolState(param);
664           
665           if (new AnnotationFile().readAnnotationFile(
666               currentAlignFrame.viewport.getAlignment(),
667               param,
668               protocol))
669           {
670             currentAlignFrame.alignPanel.fontChanged();
671             currentAlignFrame.alignPanel.setScrollValues(0, 0);
672           } else {
673             System.err.println("Annotations were not added from annotation file '"+param+"'");
674           }
675
676         }
677
678         param = getParameter("jnetfile");
679         if (param != null)
680         {
681           try
682           {
683             param = setProtocolState(param);
684             jalview.io.JPredFile predictions = new jalview.io.JPredFile(
685                 param, protocol);
686             JnetAnnotationMaker.add_annotation(predictions,
687                 currentAlignFrame.viewport.getAlignment(),
688                 0, false); // false==do not add sequence profile from concise output
689             currentAlignFrame.alignPanel.fontChanged();
690             currentAlignFrame.alignPanel.setScrollValues(0, 0);
691           }
692           catch (Exception ex)
693           {
694             ex.printStackTrace();
695           }
696         }
697
698         /*
699          <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B PDB|1GAQ|1GAQ|C">
700
701          <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
702
703          <param name="PDBfile3" value="1q0o Q45135_9MICO">
704         */
705
706
707         int pdbFileCount = 0;
708         do{
709           if (pdbFileCount > 0)
710             param = getParameter("PDBFILE" + pdbFileCount);
711           else
712             param = getParameter("PDBFILE");
713
714           if (param != null)
715           {
716             PDBEntry pdb = new PDBEntry();
717
718             String seqstring;
719             SequenceI[] seqs = null;
720             String [] chains = null;
721
722             StringTokenizer st = new StringTokenizer(param, " ");
723
724             if (st.countTokens() < 2)
725             {
726               String sequence = applet.getParameter("PDBSEQ");
727               if (sequence != null)
728                 seqs = new SequenceI[]
729                     {
730                     (Sequence) currentAlignFrame.
731                     getAlignViewport().getAlignment().
732                     findName(sequence)};
733
734             }
735             else
736             {
737               param = st.nextToken();
738               Vector tmp = new Vector();
739               Vector tmp2 = new Vector();
740
741               while (st.hasMoreTokens())
742               {
743                 seqstring = st.nextToken();
744                 StringTokenizer st2 = new StringTokenizer(seqstring,"=");
745                 if(st2.countTokens()>1)
746                 {
747                   //This is the chain
748                   tmp2.addElement(st2.nextToken());
749                   seqstring = st2.nextToken();
750                 }
751                 tmp.addElement( (Sequence) currentAlignFrame.
752                                  getAlignViewport().getAlignment().
753                                  findName(seqstring));
754               }
755
756               seqs = new SequenceI[tmp.size()];
757               tmp.copyInto(seqs);
758               if(tmp2.size()==tmp.size())
759               {
760                 chains = new String[tmp2.size()];
761                 tmp2.copyInto(chains);
762               }
763             }
764             param = setProtocolState(param);
765             
766             if (!jmolAvailable && protocol==AppletFormatAdapter.CLASSLOADER)
767             {
768               // TODO: pass PDB file in classloader on to Jmol
769               // This exception preserves the current behaviour where, even if the local pdb file was identified in the class loader
770               protocol = AppletFormatAdapter.URL; // this is probably NOT CORRECT!
771               param = addProtocol(param); // 
772             }
773
774             pdb.setFile(param);
775
776             if(seqs!=null)
777             {
778               for (int i = 0; i < seqs.length; i++)
779               {
780                 if (seqs[i]!=null)
781                 {
782                   ( (Sequence) seqs[i]).addPDBId(pdb);
783                 } else {
784                   if (JalviewLite.debug)
785                   {
786                     // this may not really be a problem but we give a warning anyway
787                     System.err.println("Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "+i+")");
788                   }
789                 }
790               }
791
792               if (jmolAvailable)
793               {
794                 new jalview.appletgui.AppletJmol(pdb,
795                                                  seqs,
796                                                  chains,
797                                                  currentAlignFrame.alignPanel,
798                                                  protocol);
799                 lastFrameX += 40;
800                 lastFrameY+=40;
801               }
802               else
803                     new MCview.AppletPDBViewer(pdb,
804                                            seqs,
805                                            chains,
806                                            currentAlignFrame.alignPanel,
807                                            protocol);
808             }
809           }
810
811           pdbFileCount++;
812         }
813         while(pdbFileCount < 10);
814         
815         /////////////////////////////
816         // modify display of features
817         //
818         // hide specific groups
819         param = getParameter("hidefeaturegroups");
820         if (param != null)
821         {
822           applet.setFeatureGroupState(param, false);
823         }
824         // show specific groups
825         param = getParameter("showfeaturegroups");
826         if (param != null)
827         {
828           applet.setFeatureGroupState(param, true);
829         }
830       }
831       else
832       {
833         fileFound = false;
834         remove(launcher);
835         repaint();
836       }
837     }
838
839     /**
840      * Discovers whether the given file is in the Applet Archive
841      * @param file String
842      * @return boolean
843      */
844     boolean inArchive(String file)
845     {
846       //This might throw a security exception in certain browsers
847       //Netscape Communicator for instance.
848       try
849       {
850         boolean rtn = (getClass().getResourceAsStream("/" + file) != null);
851         if (debug)
852         {  System.err.println("Resource '"+file+"' was "+(rtn ? "" : "not") +" located by classloader.");
853         }
854         return rtn;
855       }
856       catch (Exception ex)
857       {
858         System.out.println("Exception checking resources: " + file + " " + ex);
859         return false;
860       }
861     }
862
863     String addProtocol(String file)
864     {
865       if (file.indexOf("://") == -1)
866       {
867         file = getCodeBase() + file;
868         if (debug)
869         {
870           System.err.println("Prepended codebase for resource: '"+file+"'");
871         }
872       }
873
874       return file;
875     }
876   }
877   /**
878    * @return the default alignFrame acted on by the public applet methods.
879    * May return null with an error message on System.err indicating the fact. 
880    */
881   protected AlignFrame getDefaultTargetFrame()
882   {
883     if (currentAlignFrame!=null)
884     {
885       return currentAlignFrame;
886     }
887     if (initialAlignFrame!=null)
888     {
889       return initialAlignFrame;
890     }
891     System.err.println("Implementation error: Jalview Applet API cannot work out which AlignFrame to use.");
892     return null;
893   }
894   /**
895    * separator used for separatorList
896    */
897   protected String separator = "|"; // this is a safe(ish) separator - tabs don't work for firefox
898   /**
899    * parse the string into a list
900    * @param list
901    * @return elements separated by separator
902    */
903   public String[] separatorListToArray(String list)
904   {
905     int seplen = separator.length();
906     if (list==null || list.equals(""))
907       return null;
908     java.util.Vector jv = new Vector();
909     int cp=0,pos;
910     while ((pos=list.indexOf(separator,cp))>cp)
911     {
912       jv.addElement(list.substring(cp,pos));
913       cp = pos+seplen;
914     }
915     if (cp<list.length())
916     {
917       jv.addElement(list.substring(cp));
918     }
919     if (jv.size()>0)
920     { String[] v = new String[jv.size()];
921       for (int i=0; i<v.length; i++)
922       {
923         v[i] = (String) jv.elementAt(i);
924       }
925       jv.removeAllElements();
926       if (debug)
927       {
928         System.err.println("Array from '"+separator+"' separated List:\n"+v.length);
929         for (int i=0; i<v.length;i++)
930         {
931           System.err.println("item "+i+" '"+v[i]+"'");
932         }
933       }
934       return v;
935     }
936     if (debug)
937     {
938       System.err.println("Empty Array from '"+separator+"' separated List");
939     }
940     return null;
941   }
942   /**
943    * concatenate the list with separator
944    * @param list
945    * @return concatenated string
946    */
947   public String arrayToSeparatorList(String[] list)
948   {
949     StringBuffer v = new StringBuffer();
950     if (list!=null)
951     {
952       for (int i=0,iSize=list.length-1;i<iSize;i++)
953       { 
954         if (list[i]!=null)
955         {  
956           v.append(list[i]); 
957         }
958         v.append(separator);
959       }
960       if (list[list.length-1]!=null)
961       { v.append(list[list.length-1]);
962       }
963       if (debug)
964       {
965         System.err.println("Returning '"+separator+"' separated List:\n");
966         System.err.println(v);
967       }
968       return v.toString();
969     }
970     if (debug)
971     {
972       System.err.println("Returning empty '"+separator+"' separated List\n");
973     }
974     return "";
975   }
976   /**
977    * @return
978    * @see jalview.appletgui.AlignFrame#getFeatureGroups()
979    */
980   public String getFeatureGroups()
981   {
982     String lst = arrayToSeparatorList(getDefaultTargetFrame().getFeatureGroups());
983     return lst;
984   }
985   /**
986    * @param alf alignframe to get feature groups on
987    * @return
988    * @see jalview.appletgui.AlignFrame#getFeatureGroups()
989    */
990   public String getFeatureGroupsOn(AlignFrame alf)
991   {
992     String lst = arrayToSeparatorList(alf.getFeatureGroups());
993     return lst;
994   }
995   /**
996    * @param visible
997    * @return
998    * @see jalview.appletgui.AlignFrame#getFeatureGroupsOfState(boolean)
999    */
1000   public String getFeatureGroupsOfState(boolean visible)
1001   {
1002     return arrayToSeparatorList(getDefaultTargetFrame().getFeatureGroupsOfState(visible));
1003   }
1004   /**
1005    * @param alf align frame to get groups of state visible
1006    * @param visible
1007    * @return
1008    * @see jalview.appletgui.AlignFrame#getFeatureGroupsOfState(boolean)
1009    */
1010   public String getFeatureGroupsOfStateOn(AlignFrame alf, boolean visible)
1011   {
1012     return arrayToSeparatorList(alf.getFeatureGroupsOfState(visible));
1013   }  /**
1014    * @param groups tab separated list of group names 
1015    * @param state true or false
1016    * @see jalview.appletgui.AlignFrame#setFeatureGroupState(java.lang.String[], boolean)
1017    */
1018   public void setFeatureGroupStateOn(AlignFrame alf, String groups, boolean state)
1019   {
1020     boolean st = state;//!(state==null || state.equals("") || state.toLowerCase().equals("false"));
1021     alf.setFeatureGroupState(separatorListToArray(groups), st);
1022   }
1023   public void setFeatureGroupState(String groups, boolean state)
1024   {
1025     setFeatureGroupStateOn(getDefaultTargetFrame(), groups, state);
1026   }
1027   /**
1028    * List separator string
1029    * @return the separator
1030    */
1031   public String getSeparator()
1032   {
1033     return separator;
1034   }
1035   /**
1036    * List separator string
1037    * @param separator the separator to set
1038    */
1039   public void setSeparator(String separator)
1040   {
1041     this.separator = separator;
1042   }
1043 }