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