82a5911917799c6e5df6d81ac74670dfad74bf58
[jalview.git] / src / jalview / appletgui / AppletJmol.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
20 package jalview.appletgui;
21
22 import java.util.*;
23 import java.awt.*;
24 import java.awt.event.*;
25
26 import jalview.datamodel.*;
27 import jalview.structure.*;
28 import jalview.io.*;
29
30 import org.jmol.api.*;
31 import org.jmol.adapter.smarter.SmarterJmolAdapter;
32
33 import org.jmol.popup.*;
34 import jalview.schemes.*;
35
36
37 public class AppletJmol extends EmbmenuFrame
38     implements  StructureListener, JmolStatusListener,
39     KeyListener, ActionListener, ItemListener
40
41 {
42   Menu fileMenu = new Menu("File");
43   Menu viewMenu = new Menu("View");
44   Menu coloursMenu = new Menu("Colours");
45   Menu chainMenu = new Menu("Show Chain");
46   Menu helpMenu = new Menu("Help");
47   MenuItem mappingMenuItem = new MenuItem("View Mapping");
48
49   CheckboxMenuItem seqColour = new CheckboxMenuItem("By Sequence", true);
50   MenuItem chain = new MenuItem("By Chain");
51   MenuItem charge = new MenuItem("Charge & Cysteine");
52   MenuItem zappo = new MenuItem("Zappo");
53   MenuItem taylor = new MenuItem("Taylor");
54   MenuItem hydro = new MenuItem("Hydrophobicity");
55   MenuItem helix = new MenuItem("Helix Propensity");
56   MenuItem strand = new MenuItem("Strand Propensity");
57   MenuItem turn = new MenuItem("Turn Propensity");
58   MenuItem buried = new MenuItem("Buried Index");
59   MenuItem user = new MenuItem("User Defined Colours");
60
61   MenuItem jmolHelp = new MenuItem("Jmol Help");
62
63   JmolViewer viewer;
64   JmolPopup jmolpopup;
65
66   Panel scriptWindow;
67   TextField inputLine;
68   TextArea history;
69   SequenceI[] sequence;
70   String [] chains;
71   StructureSelectionManager ssm;
72   RenderPanel renderPanel;
73   AlignmentPanel ap;
74   String fileLoadingError;
75   boolean loadedInline;
76   PDBEntry pdbentry;
77   boolean colourBySequence = true;
78   Vector atomsPicked = new Vector();
79
80   public AppletJmol(PDBEntry pdbentry,
81                     SequenceI[] seq,
82                     String[] chains,
83                     AlignmentPanel ap,
84                     String protocol)
85   {
86     this.ap = ap;
87     this.sequence = seq;
88     this.chains = chains;
89     this.pdbentry = pdbentry;
90
91    String alreadyMapped = StructureSelectionManager
92         .getStructureSelectionManager()
93         .alreadyMappedToFile(pdbentry.getId());
94
95     if (alreadyMapped != null)
96     {
97        StructureSelectionManager.getStructureSelectionManager()
98             .setMapping(seq, chains, pdbentry.getFile(), protocol);
99        //PROMPT USER HERE TO ADD TO NEW OR EXISTING VIEW?
100        //FOR NOW, LETS JUST OPEN A NEW WINDOW
101     }
102     MenuBar menuBar = new MenuBar();
103     menuBar.add(fileMenu);
104     fileMenu.add(mappingMenuItem);
105     menuBar.add(viewMenu);
106     mappingMenuItem.addActionListener(this);
107     viewMenu.add(chainMenu);
108     menuBar.add(coloursMenu);
109     menuBar.add(helpMenu);
110
111     charge.addActionListener(this);
112     hydro.addActionListener(this);
113     chain.addActionListener(this);
114     seqColour.addItemListener(this);
115     zappo.addActionListener(this);
116     taylor.addActionListener(this);
117     helix.addActionListener(this);
118     strand.addActionListener(this);
119     turn.addActionListener(this);
120     buried.addActionListener(this);
121     user.addActionListener(this);
122
123     jmolHelp.addActionListener(this);
124
125     coloursMenu.add(seqColour);
126     coloursMenu.add(chain);
127     coloursMenu.add(charge);
128     coloursMenu.add(zappo);
129     coloursMenu.add(taylor);
130     coloursMenu.add(hydro);
131     coloursMenu.add(helix);
132     coloursMenu.add(strand);
133     coloursMenu.add(turn);
134     coloursMenu.add(buried);
135     coloursMenu.add(user);
136
137     helpMenu.add(jmolHelp);
138
139     setMenuBar(menuBar);
140
141     renderPanel = new RenderPanel();
142     embedMenuIfNeeded(renderPanel);
143     this.add(renderPanel, BorderLayout.CENTER);
144     viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter());
145
146     viewer.setAppletContext("jalview",
147                        ap.av.applet.getDocumentBase(),
148                             ap.av.applet.getCodeBase(),
149                             null);
150
151     viewer.setJmolStatusListener(this);
152
153     jmolpopup = JmolPopup.newJmolPopup(viewer);
154
155     this.addWindowListener(new WindowAdapter()
156         {
157           public void windowClosing(WindowEvent evt)
158           {
159             closeViewer();
160           }
161         });
162
163
164     if(pdbentry.getFile()!=null)
165     {
166       // import structure data from pdbentry.getFile based on given protocol
167       if (protocol.equals(AppletFormatAdapter.PASTE))
168       { 
169         loadInline(pdbentry.getFile());
170       } else
171       if (protocol.equals(AppletFormatAdapter.FILE) || protocol.equals(AppletFormatAdapter.URL)){
172           viewer.openFile(pdbentry.getFile());
173       }
174       else
175         {
176         // probably CLASSLOADER based datasource..
177         // Try and get a reader on the datasource, and pass that to Jmol
178         try {
179           FileParse fparser = new jalview.io.FileParse(protocol, pdbentry.getFile());
180           if (!fparser.isValid())
181           {
182             throw new Exception("Invalid datasource. "+fparser.getWarningMessage());
183           }
184           viewer.openReader(pdbentry.getFile(), pdbentry.getId(), fparser.getReader());
185         } catch (Exception e)
186         {
187           // give up!
188           System.err.println("Couldn't access pdbentry id="+pdbentry.getId()+" and file="+pdbentry.getFile()+" using protocol="+protocol);
189           e.printStackTrace();
190         }
191         }
192     }
193     
194     jalview.bin.JalviewLite.addFrame(this, "Jmol", 400,400);
195   }
196
197   public void loadInline(String string)
198   {
199     loadedInline = true;
200     viewer.openStringInline(string);
201   }
202
203
204   void setChainMenuItems(Vector chains)
205   {
206     chainMenu.removeAll();
207
208     MenuItem menuItem = new MenuItem("All");
209     menuItem.addActionListener(this);
210
211     chainMenu.add(menuItem);
212
213     CheckboxMenuItem menuItemCB;
214     for (int c = 0; c < chains.size(); c++)
215     {
216       menuItemCB = new CheckboxMenuItem(chains.elementAt(c).toString(), true);
217       menuItemCB.addItemListener(this);
218       chainMenu.add(menuItemCB);
219     }
220   }
221
222   boolean allChainsSelected = false;
223   void centerViewer()
224   {
225     StringBuffer cmd = new StringBuffer();
226     for (int i = 0; i < chainMenu.getItemCount(); i++)
227     {
228       if (chainMenu.getItem(i) instanceof CheckboxMenuItem)
229       {
230         CheckboxMenuItem item = (CheckboxMenuItem) chainMenu.getItem(i);
231         if (item.getState())
232           cmd.append(":" + item.getLabel() + " or ");
233       }
234     }
235
236     if (cmd.length() > 0)
237       cmd.setLength(cmd.length() - 4);
238
239     viewer.evalString("select *;restrict "
240                       + cmd + ";cartoon;center " + cmd);
241   }
242
243
244   void closeViewer()
245   {
246     viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
247     viewer.evalStringQuiet("zap");
248     viewer.setJmolStatusListener(null);
249     viewer = null;
250
251     //We'll need to find out what other
252     // listeners need to be shut down in Jmol
253     StructureSelectionManager
254         .getStructureSelectionManager()
255         .removeStructureViewerListener(this, pdbentry.getId());
256
257     this.setVisible(false);
258   }
259
260   public void actionPerformed(ActionEvent evt)
261   {
262     if(evt.getSource()==mappingMenuItem)
263     {
264       jalview.appletgui.CutAndPasteTransfer cap
265           = new jalview.appletgui.CutAndPasteTransfer(false, null);
266       Frame frame = new Frame();
267       frame.add(cap);
268
269       jalview.bin.JalviewLite.addFrame(frame, "PDB - Sequence Mapping", 550,
270                                        600);
271       cap.setText(
272           StructureSelectionManager.getStructureSelectionManager().printMapping(
273               pdbentry.getFile())
274           );
275     }
276     else if (evt.getSource() == charge)
277     {
278       colourBySequence = false;
279       seqColour.setState(false);
280       viewer.evalStringQuiet("select *;color white;select ASP,GLU;color red;"
281                       +"select LYS,ARG;color blue;select CYS;color yellow");
282     }
283
284     else if (evt.getSource() == chain)
285     {
286       colourBySequence = false;
287       seqColour.setState(false);
288       viewer.evalStringQuiet("select *;color chain");
289     }
290     else if (evt.getSource() == zappo)
291     {
292       setJalviewColourScheme(new ZappoColourScheme());
293     }
294     else if (evt.getSource() == taylor)
295     {
296      setJalviewColourScheme(new TaylorColourScheme());
297     }
298     else if (evt.getSource() == hydro)
299     {
300       setJalviewColourScheme(new HydrophobicColourScheme());
301     }
302     else if (evt.getSource() == helix)
303     {
304       setJalviewColourScheme(new HelixColourScheme());
305     }
306     else if (evt.getSource() == strand)
307     {
308       setJalviewColourScheme(new StrandColourScheme());
309     }
310     else if (evt.getSource() == turn)
311     {
312       setJalviewColourScheme(new TurnColourScheme());
313     }
314     else if (evt.getSource() == buried)
315     {
316       setJalviewColourScheme(new BuriedColourScheme());
317     }
318     else if (evt.getSource() == user)
319     {
320       new UserDefinedColours(this);
321     }
322     else if(evt.getSource() == jmolHelp)
323     {
324       try{
325         ap.av.applet.getAppletContext().showDocument(
326             new java.net.URL("http://jmol.sourceforge.net/docs/JmolUserGuide/"),
327             "jmolHelp");
328       }catch(java.net.MalformedURLException ex){}
329     }
330     else
331     {
332       allChainsSelected = true;
333       for (int i = 0; i < chainMenu.getItemCount(); i++)
334       {
335         if (chainMenu.getItem(i) instanceof CheckboxMenuItem)
336           ( (CheckboxMenuItem) chainMenu.getItem(i)).setState(true);
337       }
338       centerViewer();
339       allChainsSelected = false;
340     }
341   }
342
343   public void setJalviewColourScheme(ColourSchemeI cs)
344   {
345     colourBySequence = false;
346     seqColour.setState(false);
347
348     if(cs==null)
349       return;
350
351     String res;
352     int index;
353     Color col;
354
355     Enumeration en = ResidueProperties.aa3Hash.keys();
356     StringBuffer command = new StringBuffer("select *;color white;");
357     while(en.hasMoreElements())
358     {
359       res = en.nextElement().toString();
360       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
361       if(index>20)
362         continue;
363
364       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
365
366       command.append("select "+res+";color["
367                         + col.getRed() + ","
368                         + col.getGreen() + ","
369                         + col.getBlue() + "];");
370     }
371
372     viewer.evalStringQuiet(command.toString());
373   }
374
375   public void itemStateChanged(ItemEvent evt)
376   {
377     if (evt.getSource() == seqColour)
378     {
379       lastCommand = null;
380       colourBySequence = seqColour.getState();
381       colourBySequence(ap);
382     }
383     else if (!allChainsSelected)
384       centerViewer();
385   }
386
387   public void keyPressed(KeyEvent evt)
388   {
389     if (evt.getKeyCode() == KeyEvent.VK_ENTER
390         && scriptWindow.isVisible())
391     {
392       viewer.evalString(inputLine.getText());
393       history.append("\n$ "+inputLine.getText());
394       inputLine.setText("");
395     }
396
397   }
398
399   public void keyTyped(KeyEvent evt)
400   {  }
401
402   public void keyReleased(KeyEvent evt){}
403
404   //////////////////////////////////
405   ///StructureListener
406   public String getPdbFile()
407   {
408     return "???";
409   }
410
411
412
413   String lastMessage;
414   public void mouseOverStructure(int atomIndex, String strInfo)
415   {
416       int pdbResNum;
417
418       int chainSeparator = strInfo.indexOf(":");
419
420       if(chainSeparator==-1)
421         chainSeparator = strInfo.indexOf(".");
422
423       pdbResNum = Integer.parseInt(
424           strInfo.substring(strInfo.indexOf("]")+ 1, chainSeparator));
425
426       String chainId;
427
428       if (strInfo.indexOf(":") > -1)
429         chainId = strInfo.substring
430             (strInfo.indexOf(":")+1, strInfo.indexOf("."));
431       else
432       {
433         chainId = " ";
434       }
435
436       if (lastMessage == null || !lastMessage.equals(strInfo))
437         ssm.mouseOverStructure(pdbResNum, chainId, pdbentry.getFile());
438
439       lastMessage = strInfo;
440   }
441
442   StringBuffer resetLastRes = new StringBuffer();
443   StringBuffer eval = new StringBuffer();
444
445   public void highlightAtom(int atomIndex, int pdbResNum, String chain, String pdbfile)
446   {
447     if (!pdbfile.equals(pdbentry.getFile()))
448       return;
449
450     if (resetLastRes.length() > 0)
451     {
452       viewer.evalStringQuiet(resetLastRes.toString());
453     }
454
455     eval.setLength(0);
456     eval.append("select " + pdbResNum);
457
458     resetLastRes.setLength(0);
459     resetLastRes.append("select " + pdbResNum);
460
461     if (!chain.equals(" "))
462     {
463       eval.append(":" + chain);
464       resetLastRes.append(":" + chain);
465     }
466
467     eval.append(";wireframe 100;"+eval.toString()+".CA;");
468
469     resetLastRes.append(";wireframe 0;"+resetLastRes.toString()+".CA;spacefill 0;");
470
471     eval.append("spacefill 200;select none");
472
473     viewer.evalStringQuiet(eval.toString());
474
475   }
476
477   public void updateColours(Object source)
478   {
479     colourBySequence( (AlignmentPanel) source);
480   }
481
482 //End StructureListener
483 ////////////////////////////
484
485   public Color getColour(int atomIndex, int pdbResNum, String chain, String pdbfile)
486   {
487     if (!pdbfile.equals(pdbentry.getFile()))
488       return null;
489
490     return new Color(viewer.getAtomArgb(atomIndex));
491   }
492
493   String lastCommand;
494   FeatureRenderer fr=null;
495   public void colourBySequence(AlignmentPanel sourceap)
496   {
497     this.ap = sourceap;
498
499     if (!colourBySequence)
500       return;
501
502     StructureMapping[] mapping = ssm.getMapping(pdbentry.getFile());
503
504     if (mapping.length < 1)
505       return;
506
507     SequenceRenderer sr = new SequenceRenderer(ap.av);
508
509     boolean showFeatures = false;
510
511     if (ap.av.showSequenceFeatures)
512     {
513       showFeatures = true;
514       if (fr == null)
515       {
516         fr = new jalview.appletgui.FeatureRenderer(ap.av);
517       }
518
519       fr.transferSettings(ap.seqPanel.seqCanvas.getFeatureRenderer());
520     }
521
522     StringBuffer command = new StringBuffer();
523
524     int lastPos = -1;
525     for (int s = 0; s < sequence.length; s++)
526     {
527       for (int sp,m = 0; m < mapping.length; m++)
528       {
529         if (mapping[m].getSequence() == sequence[s]
530             && (sp=ap.av.alignment.findIndex(sequence[s])) > -1)
531         {
532           SequenceI asp = ap.av.alignment.getSequenceAt(sp);
533           for (int r = 0; r < asp.getLength(); r++)
534           {
535             // no mapping to gaps in sequence
536             if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
537             {
538               continue;
539             }
540             int pos = mapping[m].getPDBResNum(
541                     asp.findPosition(r));
542
543
544             if (pos < 1 || pos == lastPos)
545               continue;
546
547             lastPos = pos;
548
549             Color col = sr.getResidueBoxColour(sequence[s], r);
550
551             if (showFeatures)
552               col = fr.findFeatureColour(col, sequence[s], r);
553
554             if (command.toString().endsWith(":" + mapping[m].getChain() +
555                                             ";color["
556                                             + col.getRed() + ","
557                                             + col.getGreen() + ","
558                                             + col.getBlue() + "]"))
559             {
560               command = condenseCommand(command.toString(), pos);
561               continue;
562             }
563
564             command.append(";select " + pos);
565
566             if (!mapping[m].getChain().equals(" "))
567             {
568               command.append(":" + mapping[m].getChain());
569             }
570
571             command.append(";color["
572                            + col.getRed() + ","
573                            + col.getGreen() + ","
574                            + col.getBlue() + "]");
575           }
576           break;
577         }
578       }
579     }
580
581     if (lastCommand == null || !lastCommand.equals(command.toString()))
582     {
583       viewer.evalStringQuiet(command.toString());
584     }
585     lastCommand = command.toString();
586   }
587
588
589   StringBuffer condenseCommand(String command, int pos)
590   {
591
592     StringBuffer sb = new StringBuffer(command.substring(0, command.lastIndexOf("select")+7));
593
594     command = command.substring(sb.length());
595
596     String start;
597
598     if (command.indexOf("-") > -1)
599     {
600       start = command.substring(0,command.indexOf("-"));
601     }
602     else
603     {
604       start = command.substring(0, command.indexOf(":"));
605     }
606
607     sb.append(start+"-"+pos+command.substring(command.indexOf(":")));
608
609     return sb;
610   }
611
612   /////////////////////////////////
613   //JmolStatusListener
614
615   public String eval(String strEval)
616   {
617    // System.out.println(strEval);
618    //"# 'eval' is implemented only for the applet.";
619     return null;
620   }
621
622   public void createImage(String file, String type, int quality)
623   {}
624
625   public void setCallbackFunction(String callbackType,
626                                   String callbackFunction)
627   {}
628
629   public void notifyFileLoaded(String fullPathName, String fileName,
630                                String modelName, Object clientFile,
631                                String errorMsg)
632   {
633     if(errorMsg!=null)
634     {
635       fileLoadingError = errorMsg;
636       repaint();
637       return;
638     }
639
640     fileLoadingError = null;
641
642     if (fileName != null)
643     {
644       //FILE LOADED OK
645       jmolpopup.updateComputedMenus();
646             viewer.evalStringQuiet(
647           "select backbone;restrict;cartoon;wireframe off;spacefill off");
648
649       ssm = StructureSelectionManager.getStructureSelectionManager();
650
651       MCview.PDBfile pdb;
652       if (loadedInline)
653       {
654         pdb = ssm.setMapping(sequence,chains,
655                                 pdbentry.getFile(),
656                                 AppletFormatAdapter.PASTE);
657         pdbentry.setFile("INLINE"+pdb.id);
658       }
659       else
660       {
661          pdb = ssm.setMapping(sequence,chains,
662                               pdbentry.getFile(),
663                               AppletFormatAdapter.URL);
664       }
665
666       pdbentry.setId(pdb.id);
667
668       ssm.addStructureViewerListener(this);
669
670       Vector chains = new Vector();
671       for (int i = 0; i < pdb.chains.size(); i++)
672       {
673         chains.addElement( ( (MCview.PDBChain) pdb.chains.elementAt(i)).id);
674       }
675       setChainMenuItems(chains);
676
677       colourBySequence(ap);
678
679       StringBuffer title = new StringBuffer(sequence[0].getName() + ":" +
680                                             pdbentry.getId());
681
682       if (pdbentry.getProperty() != null)
683       {
684         if (pdbentry.getProperty().get("method") != null)
685         {
686           title.append(" Method: ");
687           title.append(pdbentry.getProperty().get("method"));
688         }
689         if (pdbentry.getProperty().get("chains") != null)
690         {
691           title.append(" Chain:");
692           title.append(pdbentry.getProperty().get("chains"));
693         }
694       }
695
696       this.setTitle(title.toString());
697
698     }
699     else
700       return;
701   }
702
703   public void notifyFrameChanged(int frameNo)
704   {
705     boolean isAnimationRunning = (frameNo <= -2);
706   }
707
708   public void notifyScriptStart(String statusMessage, String additionalInfo)
709   {}
710
711   public void sendConsoleEcho(String strEcho)
712   {
713     if (scriptWindow == null)
714       showConsole(true);
715
716     history.append("\n"+strEcho);
717   }
718
719   public void sendConsoleMessage(String strStatus)
720   {
721     if(history!=null && strStatus!=null
722        && !strStatus.equals("Script completed"))
723     {
724       history.append("\n"+strStatus);
725     }
726   }
727
728   public void notifyScriptTermination(String strStatus, int msWalltime)
729   {  }
730
731   public void handlePopupMenu(int x, int y)
732   {
733     jmolpopup.show(x, y);
734   }
735
736   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
737   {
738     notifyAtomPicked(iatom, strMeasure);
739   }
740
741   public void notifyNewDefaultModeMeasurement(int count, String strInfo)
742   {}
743
744   public void notifyAtomPicked(int atomIndex, String strInfo)
745   {
746
747     int chainSeparator = strInfo.indexOf(":");
748
749     if(chainSeparator==-1)
750       chainSeparator = strInfo.indexOf(".");
751
752     String picked =
753         strInfo.substring(strInfo.indexOf("]")+ 1, chainSeparator);
754
755
756     if (strInfo.indexOf(":") > -1)
757       picked+=strInfo.substring(strInfo.indexOf(":")+1,
758                                strInfo.indexOf("."));
759
760     picked+=".CA";
761
762     if (!atomsPicked.contains(picked))
763     {
764       viewer.evalString("select "+picked+";label %n %r:%c");
765       atomsPicked.addElement(picked);
766     }
767     else
768     {
769       viewer.evalString("select "+picked+";label off");
770       atomsPicked.removeElement(picked);
771     }
772   }
773
774   public void notifyAtomHovered(int atomIndex, String strInfo)
775   {
776     mouseOverStructure(atomIndex, strInfo);
777   }
778
779   public void sendSyncScript(String script, String appletName)
780   {}
781
782   public void showUrl(String url)
783   {
784     try{
785       ap.av.applet.getAppletContext().showDocument(new java.net.URL(url),
786           "jmolOutput");
787     }catch(java.net.MalformedURLException ex)
788     {}
789   }
790
791   public void showConsole(boolean showConsole)
792   {
793     if (scriptWindow == null)
794     {
795       scriptWindow = new Panel(new BorderLayout());
796       inputLine = new TextField();
797       history = new TextArea(5, 40);
798       scriptWindow.add(history, BorderLayout.CENTER);
799       scriptWindow.add(inputLine, BorderLayout.SOUTH);
800       add(scriptWindow, BorderLayout.SOUTH);
801       scriptWindow.setVisible(false);
802       history.setEditable(false);
803       inputLine.addKeyListener(this);
804     }
805
806     scriptWindow.setVisible(!scriptWindow.isVisible());
807     validate();
808   }
809
810   public float functionXY(String functionName, int x, int y)
811   {
812     return 0;
813   }
814
815   ///End JmolStatusListener
816   ///////////////////////////////
817
818
819   class RenderPanel
820       extends Panel
821   {
822     Dimension currentSize = new Dimension();
823     Rectangle rectClip = new Rectangle();
824
825     public void update(Graphics g) {
826       paint(g);
827     }
828     public void paint(Graphics g)
829     {
830       currentSize = this.getSize();
831       rectClip = g.getClipBounds();
832
833       if (viewer == null)
834       {
835         g.setColor(Color.black);
836         g.fillRect(0, 0, currentSize.width, currentSize.height);
837         g.setColor(Color.white);
838         g.setFont(new Font("Verdana", Font.BOLD, 14));
839         g.drawString("Retrieving PDB data....", 20, currentSize.height / 2);
840       }
841       else
842       {
843         viewer.renderScreenImage(g, currentSize, rectClip);
844       }
845     }
846   }
847
848 }