update to Jmol series 12 (JAL-582)
[jalview.git] / src / jalview / appletgui / AppletJmol.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.appletgui;
19
20 import java.util.*;
21 import java.awt.*;
22 import java.awt.event.*;
23
24 import jalview.datamodel.*;
25 import jalview.structure.*;
26 import jalview.io.*;
27
28 import org.jmol.api.*;
29 import org.jmol.adapter.smarter.SmarterJmolAdapter;
30
31 import org.jmol.popup.*;
32 import org.jmol.viewer.JmolConstants;
33
34 import jalview.schemes.*;
35
36 public class AppletJmol extends EmbmenuFrame implements StructureListener,
37         JmolStatusListener, KeyListener, ActionListener, ItemListener
38
39 {
40   Menu fileMenu = new Menu("File");
41
42   Menu viewMenu = new Menu("View");
43
44   Menu coloursMenu = new Menu("Colours");
45
46   Menu chainMenu = new Menu("Show Chain");
47
48   Menu helpMenu = new Menu("Help");
49
50   MenuItem mappingMenuItem = new MenuItem("View Mapping");
51
52   CheckboxMenuItem seqColour = new CheckboxMenuItem("By Sequence", true);
53
54   MenuItem chain = new MenuItem("By Chain");
55
56   MenuItem charge = new MenuItem("Charge & Cysteine");
57
58   MenuItem zappo = new MenuItem("Zappo");
59
60   MenuItem taylor = new MenuItem("Taylor");
61
62   MenuItem hydro = new MenuItem("Hydrophobicity");
63
64   MenuItem helix = new MenuItem("Helix Propensity");
65
66   MenuItem strand = new MenuItem("Strand Propensity");
67
68   MenuItem turn = new MenuItem("Turn Propensity");
69
70   MenuItem buried = new MenuItem("Buried Index");
71
72   MenuItem user = new MenuItem("User Defined Colours");
73
74   MenuItem jmolHelp = new MenuItem("Jmol Help");
75
76   JmolViewer viewer;
77
78   JmolPopup jmolpopup;
79
80   Panel scriptWindow;
81
82   TextField inputLine;
83
84   TextArea history;
85
86   SequenceI[] sequence;
87
88   String[] chains;
89
90   StructureSelectionManager ssm;
91
92   RenderPanel renderPanel;
93
94   AlignmentPanel ap;
95
96   String fileLoadingError;
97
98   boolean loadedInline;
99
100   PDBEntry pdbentry;
101
102   boolean colourBySequence = true;
103
104   Vector atomsPicked = new Vector();
105
106   /**
107    * datasource protocol for access to PDBEntry
108    */
109   String protocol = null;
110
111   public AppletJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
112           AlignmentPanel ap, String protocol)
113   {
114     this.ap = ap;
115     this.sequence = seq;
116     this.chains = chains;
117     this.pdbentry = pdbentry;
118     this.protocol = protocol;
119     if (pdbentry.getId() == null || pdbentry.getId().length() < 1)
120     {
121       if (protocol.equals(AppletFormatAdapter.PASTE))
122       {
123         pdbentry.setId("PASTED PDB"
124                 + (chains == null ? "_" : chains.toString()));
125       }
126       else
127       {
128         pdbentry.setId(pdbentry.getFile());
129       }
130     }
131
132     if (jalview.bin.JalviewLite.debug)
133     {
134       System.err
135               .println("AppletJmol: PDB ID is '" + pdbentry.getId() + "'");
136     }
137
138     String alreadyMapped = StructureSelectionManager
139             .getStructureSelectionManager().alreadyMappedToFile(
140                     pdbentry.getId());
141     MCview.PDBfile reader = null;
142     if (alreadyMapped != null)
143     {
144       reader = StructureSelectionManager.getStructureSelectionManager()
145               .setMapping(seq, chains, pdbentry.getFile(), protocol);
146       // PROMPT USER HERE TO ADD TO NEW OR EXISTING VIEW?
147       // FOR NOW, LETS JUST OPEN A NEW WINDOW
148     }
149     MenuBar menuBar = new MenuBar();
150     menuBar.add(fileMenu);
151     fileMenu.add(mappingMenuItem);
152     menuBar.add(viewMenu);
153     mappingMenuItem.addActionListener(this);
154     viewMenu.add(chainMenu);
155     menuBar.add(coloursMenu);
156     menuBar.add(helpMenu);
157
158     charge.addActionListener(this);
159     hydro.addActionListener(this);
160     chain.addActionListener(this);
161     seqColour.addItemListener(this);
162     zappo.addActionListener(this);
163     taylor.addActionListener(this);
164     helix.addActionListener(this);
165     strand.addActionListener(this);
166     turn.addActionListener(this);
167     buried.addActionListener(this);
168     user.addActionListener(this);
169
170     jmolHelp.addActionListener(this);
171
172     coloursMenu.add(seqColour);
173     coloursMenu.add(chain);
174     coloursMenu.add(charge);
175     coloursMenu.add(zappo);
176     coloursMenu.add(taylor);
177     coloursMenu.add(hydro);
178     coloursMenu.add(helix);
179     coloursMenu.add(strand);
180     coloursMenu.add(turn);
181     coloursMenu.add(buried);
182     coloursMenu.add(user);
183
184     helpMenu.add(jmolHelp);
185
186     setMenuBar(menuBar);
187
188     renderPanel = new RenderPanel();
189     embedMenuIfNeeded(renderPanel);
190     this.add(renderPanel, BorderLayout.CENTER);
191     viewer = JmolViewer.allocateViewer(renderPanel,
192             new SmarterJmolAdapter(), "jalviewJmol", ap.av.applet.getDocumentBase(),
193             ap.av.applet.getCodeBase(), "", this);
194
195     jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
196
197     this.addWindowListener(new WindowAdapter()
198     {
199       public void windowClosing(WindowEvent evt)
200       {
201         closeViewer();
202       }
203     });
204
205     if (pdbentry.getFile() != null)
206     {
207       // import structure data from pdbentry.getFile based on given protocol
208       if (protocol.equals(AppletFormatAdapter.PASTE))
209       {
210         loadInline(pdbentry.getFile());
211       }
212       else if (protocol.equals(AppletFormatAdapter.FILE)
213               || protocol.equals(AppletFormatAdapter.URL))
214       {
215         viewer.openFile(pdbentry.getFile());
216       }
217       else
218       {
219         // probably CLASSLOADER based datasource..
220         // Try and get a reader on the datasource, and pass that to Jmol
221         try
222         {
223           java.io.Reader freader = null;
224           if (reader != null)
225           {
226             if (jalview.bin.JalviewLite.debug)
227             {
228               System.err
229                       .println("AppletJmol:Trying to reuse existing PDBfile IO parser.");
230             }
231             // re-use the one we opened earlier
232             freader = reader.getReader();
233           }
234           if (freader == null)
235           {
236             if (jalview.bin.JalviewLite.debug)
237             {
238               System.err
239                       .println("AppletJmol:Creating new PDBfile IO parser.");
240             }
241             FileParse fp = new FileParse(pdbentry.getFile(), protocol);
242             fp.mark();
243             // reader = new MCview.PDBfile(fp);
244             // could set ID, etc.
245             // if (!reader.isValid())
246             // {
247             // throw new Exception("Invalid datasource.
248             // "+reader.getWarningMessage());
249             // }
250             // fp.reset();
251             freader = fp.getReader();
252           }
253           if (freader == null)
254           {
255             throw new Exception(
256                     "Invalid datasource. Could not obtain Reader.");
257           }
258           viewer.openReader(pdbentry.getFile(), pdbentry.getId(), freader);
259         } catch (Exception e)
260         {
261           // give up!
262           System.err.println("Couldn't access pdbentry id="
263                   + pdbentry.getId() + " and file=" + pdbentry.getFile()
264                   + " using protocol=" + protocol);
265           e.printStackTrace();
266         }
267       }
268     }
269
270     jalview.bin.JalviewLite.addFrame(this, "Jmol", 400, 400);
271   }
272
273   public void loadInline(String string)
274   {
275     loadedInline = true;
276     viewer.openStringInline(string);
277   }
278
279   void setChainMenuItems(Vector chains)
280   {
281     chainMenu.removeAll();
282
283     MenuItem menuItem = new MenuItem("All");
284     menuItem.addActionListener(this);
285
286     chainMenu.add(menuItem);
287
288     CheckboxMenuItem menuItemCB;
289     for (int c = 0; c < chains.size(); c++)
290     {
291       menuItemCB = new CheckboxMenuItem(chains.elementAt(c).toString(),
292               true);
293       menuItemCB.addItemListener(this);
294       chainMenu.add(menuItemCB);
295     }
296   }
297
298   boolean allChainsSelected = false;
299
300   void centerViewer()
301   {
302     StringBuffer cmd = new StringBuffer();
303     for (int i = 0; i < chainMenu.getItemCount(); i++)
304     {
305       if (chainMenu.getItem(i) instanceof CheckboxMenuItem)
306       {
307         CheckboxMenuItem item = (CheckboxMenuItem) chainMenu.getItem(i);
308         if (item.getState())
309           cmd.append(":" + item.getLabel() + " or ");
310       }
311     }
312
313     if (cmd.length() > 0)
314       cmd.setLength(cmd.length() - 4);
315
316     viewer
317             .evalString("select *;restrict " + cmd + ";cartoon;center "
318                     + cmd);
319   }
320
321   void closeViewer()
322   {
323     viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
324     viewer.evalStringQuiet("zap");
325     viewer.setJmolStatusListener(null);
326     viewer = null;
327
328     // We'll need to find out what other
329     // listeners need to be shut down in Jmol
330     StructureSelectionManager.getStructureSelectionManager()
331             .removeStructureViewerListener(this, pdbentry.getId());
332
333     this.setVisible(false);
334   }
335
336   public void actionPerformed(ActionEvent evt)
337   {
338     if (evt.getSource() == mappingMenuItem)
339     {
340       jalview.appletgui.CutAndPasteTransfer cap = new jalview.appletgui.CutAndPasteTransfer(
341               false, null);
342       Frame frame = new Frame();
343       frame.add(cap);
344
345       jalview.bin.JalviewLite.addFrame(frame, "PDB - Sequence Mapping",
346               550, 600);
347       cap.setText(StructureSelectionManager.getStructureSelectionManager()
348               .printMapping(pdbentry.getFile()));
349     }
350     else if (evt.getSource() == charge)
351     {
352       colourBySequence = false;
353       seqColour.setState(false);
354       viewer
355               .evalStringQuiet("select *;color white;select ASP,GLU;color red;"
356                       + "select LYS,ARG;color blue;select CYS;color yellow");
357     }
358
359     else if (evt.getSource() == chain)
360     {
361       colourBySequence = false;
362       seqColour.setState(false);
363       viewer.evalStringQuiet("select *;color chain");
364     }
365     else if (evt.getSource() == zappo)
366     {
367       setJalviewColourScheme(new ZappoColourScheme());
368     }
369     else if (evt.getSource() == taylor)
370     {
371       setJalviewColourScheme(new TaylorColourScheme());
372     }
373     else if (evt.getSource() == hydro)
374     {
375       setJalviewColourScheme(new HydrophobicColourScheme());
376     }
377     else if (evt.getSource() == helix)
378     {
379       setJalviewColourScheme(new HelixColourScheme());
380     }
381     else if (evt.getSource() == strand)
382     {
383       setJalviewColourScheme(new StrandColourScheme());
384     }
385     else if (evt.getSource() == turn)
386     {
387       setJalviewColourScheme(new TurnColourScheme());
388     }
389     else if (evt.getSource() == buried)
390     {
391       setJalviewColourScheme(new BuriedColourScheme());
392     }
393     else if (evt.getSource() == user)
394     {
395       new UserDefinedColours(this);
396     }
397     else if (evt.getSource() == jmolHelp)
398     {
399       try
400       {
401         ap.av.applet.getAppletContext().showDocument(
402                 new java.net.URL(
403                         "http://jmol.sourceforge.net/docs/JmolUserGuide/"),
404                 "jmolHelp");
405       } catch (java.net.MalformedURLException ex)
406       {
407       }
408     }
409     else
410     {
411       allChainsSelected = true;
412       for (int i = 0; i < chainMenu.getItemCount(); i++)
413       {
414         if (chainMenu.getItem(i) instanceof CheckboxMenuItem)
415           ((CheckboxMenuItem) chainMenu.getItem(i)).setState(true);
416       }
417       centerViewer();
418       allChainsSelected = false;
419     }
420   }
421
422   public void setJalviewColourScheme(ColourSchemeI cs)
423   {
424     colourBySequence = false;
425     seqColour.setState(false);
426
427     if (cs == null)
428       return;
429
430     String res;
431     int index;
432     Color col;
433
434     Enumeration en = ResidueProperties.aa3Hash.keys();
435     StringBuffer command = new StringBuffer("select *;color white;");
436     while (en.hasMoreElements())
437     {
438       res = en.nextElement().toString();
439       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
440       if (index > 20)
441         continue;
442
443       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
444
445       command.append("select " + res + ";color[" + col.getRed() + ","
446               + col.getGreen() + "," + col.getBlue() + "];");
447     }
448
449     viewer.evalStringQuiet(command.toString());
450   }
451
452   public void itemStateChanged(ItemEvent evt)
453   {
454     if (evt.getSource() == seqColour)
455     {
456       lastCommand = null;
457       colourBySequence = seqColour.getState();
458       colourBySequence(ap);
459     }
460     else if (!allChainsSelected)
461       centerViewer();
462   }
463
464   public void keyPressed(KeyEvent evt)
465   {
466     if (evt.getKeyCode() == KeyEvent.VK_ENTER && scriptWindow.isVisible())
467     {
468       viewer.evalString(inputLine.getText());
469       history.append("\n$ " + inputLine.getText());
470       inputLine.setText("");
471     }
472
473   }
474
475   public void keyTyped(KeyEvent evt)
476   {
477   }
478
479   public void keyReleased(KeyEvent evt)
480   {
481   }
482
483   // ////////////////////////////////
484   // /StructureListener
485   public String getPdbFile()
486   {
487     return "???";
488   }
489
490   String lastMessage;
491
492   public void mouseOverStructure(int atomIndex, String strInfo)
493   {
494     int pdbResNum;
495
496     int chainSeparator = strInfo.indexOf(":");
497
498     if (chainSeparator == -1)
499       chainSeparator = strInfo.indexOf(".");
500
501     pdbResNum = Integer.parseInt(strInfo.substring(
502             strInfo.indexOf("]") + 1, chainSeparator));
503
504     String chainId;
505
506     if (strInfo.indexOf(":") > -1)
507       chainId = strInfo.substring(strInfo.indexOf(":") + 1, strInfo
508               .indexOf("."));
509     else
510     {
511       chainId = " ";
512     }
513
514     if (lastMessage == null || !lastMessage.equals(strInfo))
515       ssm.mouseOverStructure(pdbResNum, chainId, pdbentry.getFile());
516
517     lastMessage = strInfo;
518   }
519
520   StringBuffer resetLastRes = new StringBuffer();
521
522   StringBuffer eval = new StringBuffer();
523
524   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
525           String pdbfile)
526   {
527     if (!pdbfile.equals(pdbentry.getFile()))
528       return;
529
530     if (resetLastRes.length() > 0)
531     {
532       viewer.evalStringQuiet(resetLastRes.toString());
533     }
534
535     eval.setLength(0);
536     eval.append("select " + pdbResNum);
537
538     resetLastRes.setLength(0);
539     resetLastRes.append("select " + pdbResNum);
540
541     eval.append(":");
542     resetLastRes.append(":");
543     if (!chain.equals(" "))
544     {
545       eval.append(chain);
546       resetLastRes.append(chain);
547     }
548
549     eval.append(";wireframe 100;" + eval.toString() + " and not hetero;");
550
551     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
552             + " and not hetero; spacefill 0;");
553
554     eval.append("spacefill 200;select none");
555
556     viewer.evalStringQuiet(eval.toString());
557
558   }
559
560   public void updateColours(Object source)
561   {
562     colourBySequence((AlignmentPanel) source);
563   }
564
565   // End StructureListener
566   // //////////////////////////
567
568   public Color getColour(int atomIndex, int pdbResNum, String chain,
569           String pdbfile)
570   {
571     if (!pdbfile.equals(pdbentry.getFile()))
572       return null;
573
574     return new Color(viewer.getAtomArgb(atomIndex));
575   }
576
577   String lastCommand;
578
579   FeatureRenderer fr = null;
580
581   public void colourBySequence(AlignmentPanel sourceap)
582   {
583     this.ap = sourceap;
584
585     if (!colourBySequence)
586       return;
587
588     StructureMapping[] mapping = ssm.getMapping(pdbentry.getFile());
589
590     if (mapping.length < 1)
591       return;
592
593     SequenceRenderer sr = new SequenceRenderer(ap.av);
594
595     boolean showFeatures = false;
596
597     if (ap.av.showSequenceFeatures)
598     {
599       showFeatures = true;
600       if (fr == null)
601       {
602         fr = new jalview.appletgui.FeatureRenderer(ap.av);
603       }
604
605       fr.transferSettings(ap.seqPanel.seqCanvas.getFeatureRenderer());
606     }
607
608     StringBuffer command = new StringBuffer();
609
610     int lastPos = -1;
611     for (int s = 0; s < sequence.length; s++)
612     {
613       for (int sp, m = 0; m < mapping.length; m++)
614       {
615         if (mapping[m].getSequence() == sequence[s]
616                 && (sp = ap.av.alignment.findIndex(sequence[s])) > -1)
617         {
618           SequenceI asp = ap.av.alignment.getSequenceAt(sp);
619           for (int r = 0; r < asp.getLength(); r++)
620           {
621             // no mapping to gaps in sequence
622             if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
623             {
624               continue;
625             }
626             int pos = mapping[m].getPDBResNum(asp.findPosition(r));
627
628             if (pos < 1 || pos == lastPos)
629               continue;
630
631             lastPos = pos;
632
633             Color col = sr.getResidueBoxColour(sequence[s], r);
634
635             if (showFeatures)
636               col = fr.findFeatureColour(col, sequence[s], r);
637
638             if (command.toString().endsWith(
639                     ":" + mapping[m].getChain() + ";color[" + col.getRed()
640                             + "," + col.getGreen() + "," + col.getBlue()
641                             + "]"))
642             {
643               command = condenseCommand(command.toString(), pos);
644               continue;
645             }
646
647             command.append(";select " + pos);
648
649             if (!mapping[m].getChain().equals(" "))
650             {
651               command.append(":" + mapping[m].getChain());
652             }
653
654             command.append(";color[" + col.getRed() + "," + col.getGreen()
655                     + "," + col.getBlue() + "]");
656           }
657           break;
658         }
659       }
660     }
661
662     if (lastCommand == null || !lastCommand.equals(command.toString()))
663     {
664       viewer.evalStringQuiet(command.toString());
665     }
666     lastCommand = command.toString();
667   }
668
669   StringBuffer condenseCommand(String command, int pos)
670   {
671
672     StringBuffer sb = new StringBuffer(command.substring(0, command
673             .lastIndexOf("select") + 7));
674
675     command = command.substring(sb.length());
676
677     String start;
678
679     if (command.indexOf("-") > -1)
680     {
681       start = command.substring(0, command.indexOf("-"));
682     }
683     else
684     {
685       start = command.substring(0, command.indexOf(":"));
686     }
687
688     sb.append(start + "-" + pos + command.substring(command.indexOf(":")));
689
690     return sb;
691   }
692
693   // ///////////////////////////////
694   // JmolStatusListener
695
696   public String eval(String strEval)
697   {
698     // System.out.println(strEval);
699     // "# 'eval' is implemented only for the applet.";
700     return null;
701   }
702
703   public void createImage(String file, String type, int quality)
704   {
705   }
706
707   public void notifyFileLoaded(String fullPathName, String fileName,
708           String modelName, String errorMsg, int modelParts)
709   {
710     if (errorMsg != null)
711     {
712       fileLoadingError = errorMsg;
713       repaint();
714       return;
715     }
716
717     fileLoadingError = null;
718
719     if (fileName != null)
720     {
721       // TODO: do some checking using the modelPts number of parts against our own estimate of the number of chains
722       // FILE LOADED OK
723       jmolpopup.updateComputedMenus();
724       viewer
725               .evalStringQuiet("select backbone;restrict;cartoon;wireframe off;spacefill off");
726
727       ssm = StructureSelectionManager.getStructureSelectionManager();
728       MCview.PDBfile pdb;
729       if (loadedInline)
730       {
731         pdb = ssm.setMapping(sequence, chains, pdbentry.getFile(),
732                 AppletFormatAdapter.PASTE);
733         pdbentry.setFile("INLINE" + pdb.id);
734       }
735       else
736       {
737         // TODO: Jmol can in principle retrieve from CLASSLOADER but this needs
738         // to be tested. See mantis bug
739         // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
740
741         pdb = ssm.setMapping(sequence, chains, pdbentry.getFile(),
742                 AppletFormatAdapter.URL);
743
744       }
745
746       pdbentry.setId(pdb.id);
747
748       ssm.addStructureViewerListener(this);
749
750       Vector chains = new Vector();
751       for (int i = 0; i < pdb.chains.size(); i++)
752       {
753         chains.addElement(((MCview.PDBChain) pdb.chains.elementAt(i)).id);
754       }
755       setChainMenuItems(chains);
756
757       colourBySequence(ap);
758
759       StringBuffer title = new StringBuffer(sequence[0].getName() + ":"
760               + pdbentry.getId());
761
762       if (pdbentry.getProperty() != null)
763       {
764         if (pdbentry.getProperty().get("method") != null)
765         {
766           title.append(" Method: ");
767           title.append(pdbentry.getProperty().get("method"));
768         }
769         if (pdbentry.getProperty().get("chains") != null)
770         {
771           title.append(" Chain:");
772           title.append(pdbentry.getProperty().get("chains"));
773         }
774       }
775
776       this.setTitle(title.toString());
777
778     }
779     else
780       return;
781   }
782
783   public void sendConsoleEcho(String strEcho)
784   {
785     if (scriptWindow == null)
786       showConsole(true);
787
788     history.append("\n" + strEcho);
789   }
790
791   public void sendConsoleMessage(String strStatus)
792   {
793     if (history != null && strStatus != null
794             && !strStatus.equals("Script completed"))
795     {
796       history.append("\n" + strStatus);
797     }
798   }
799
800   public void notifyScriptTermination(String strStatus, int msWalltime)
801   {
802   }
803
804   public void handlePopupMenu(int x, int y)
805   {
806     jmolpopup.show(x, y);
807   }
808
809   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
810   {
811     notifyAtomPicked(iatom, strMeasure, null);
812   }
813
814   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
815   {
816     if (strData!=null)
817     {
818       System.err.println("Ignoring additional pick data string "+strData);
819     }
820     int chainSeparator = strInfo.indexOf(":");
821
822     if (chainSeparator == -1)
823       chainSeparator = strInfo.indexOf(".");
824
825     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
826             chainSeparator);
827
828     if (strInfo.indexOf(":") > -1)
829       picked += strInfo.substring(strInfo.indexOf(":") + 1, strInfo
830               .indexOf("."));
831
832     picked = "(("+picked+".CA" + ")|("+picked+".P"+"))";
833
834     if (!atomsPicked.contains(picked))
835     {
836       viewer.evalString("select " + picked + ";label %n %r:%c");
837       atomsPicked.addElement(picked);
838     }
839     else
840     {
841       viewer.evalString("select " + picked + ";label off");
842       atomsPicked.removeElement(picked);
843     }
844   }
845
846   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
847   {
848     if (data!=null)
849     {
850       System.err.println("Ignoring additional hover info: "+data);
851     }
852     mouseOverStructure(atomIndex, strInfo);
853   }
854
855
856   public void showUrl(String url)
857   {
858     try
859     {
860       ap.av.applet.getAppletContext().showDocument(new java.net.URL(url),
861               "jmolOutput");
862     } catch (java.net.MalformedURLException ex)
863     {
864     }
865   }
866
867   public void showConsole(boolean showConsole)
868   {
869     if (scriptWindow == null)
870     {
871       scriptWindow = new Panel(new BorderLayout());
872       inputLine = new TextField();
873       history = new TextArea(5, 40);
874       scriptWindow.add(history, BorderLayout.CENTER);
875       scriptWindow.add(inputLine, BorderLayout.SOUTH);
876       add(scriptWindow, BorderLayout.SOUTH);
877       scriptWindow.setVisible(false);
878       history.setEditable(false);
879       inputLine.addKeyListener(this);
880     }
881
882     scriptWindow.setVisible(!scriptWindow.isVisible());
883     validate();
884   }
885
886   public float[][] functionXY(String functionName, int x, int y)
887   {
888     return null ;
889   }
890
891   // /End JmolStatusListener
892   // /////////////////////////////
893
894   class RenderPanel extends Panel
895   {
896     Dimension currentSize = new Dimension();
897
898     Rectangle rectClip = new Rectangle();
899
900     public void update(Graphics g)
901     {
902       paint(g);
903     }
904
905     public void paint(Graphics g)
906     {
907       currentSize = this.getSize();
908       rectClip = g.getClipBounds();
909
910       if (viewer == null)
911       {
912         g.setColor(Color.black);
913         g.fillRect(0, 0, currentSize.width, currentSize.height);
914         g.setColor(Color.white);
915         g.setFont(new Font("Verdana", Font.BOLD, 14));
916         g.drawString("Retrieving PDB data....", 20, currentSize.height / 2);
917       }
918       else
919       {
920         viewer.renderScreenImage(g, currentSize, rectClip);
921       }
922     }
923   }
924
925   @Override
926   public String createImage(String fileName, String type,
927           Object textOrBytes, int quality)
928   {
929     // TODO Auto-generated method stub
930     return null;
931   }
932
933   @Override
934   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
935   {
936     // TODO Auto-generated method stub
937     return null;
938   }
939
940   @Override
941   public Hashtable getRegistryInfo()
942   {
943     // TODO Auto-generated method stub
944     return null;
945   }
946
947   @Override
948   public void notifyCallback(int type, Object[] data)
949   {
950     try {
951     switch (type)
952     {
953     case JmolConstants.CALLBACK_LOADSTRUCT:
954       notifyFileLoaded((String) data[1], (String) data[2], 
955               (String) data[3], (String) data[4], ((Integer) data[5]).intValue());
956               
957       break;
958     case JmolConstants.CALLBACK_PICK:
959       notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1], (String) data[0]);
960       // also highlight in alignment
961     case JmolConstants.CALLBACK_HOVER:
962       notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1], (String) data[0]);
963       break;
964     case JmolConstants.CALLBACK_SCRIPT:
965       notifyScriptTermination((String)data[2], ((Integer)data[3]).intValue());
966       break;
967     case JmolConstants.CALLBACK_ECHO:
968       sendConsoleEcho((String)data[1]);
969       break;
970     case JmolConstants.CALLBACK_MESSAGE:
971       sendConsoleMessage((data==null) ? ((String) null) : (String)data[1]);
972       break;
973     case JmolConstants.CALLBACK_MEASURE:
974     case JmolConstants.CALLBACK_CLICK:
975       default:
976         System.err.println("Unhandled callback "+type+" "+data);
977         break;
978     }
979     }
980     catch (Exception e)
981     {
982       System.err.println("Squashed Jmol callback handler error:");
983       e.printStackTrace();
984     }
985   }
986
987   @Override
988   public boolean notifyEnabled(int callbackPick)
989   {
990     switch (callbackPick)
991     {
992     case JmolConstants.CALLBACK_ECHO:
993     case JmolConstants.CALLBACK_LOADSTRUCT:
994     case JmolConstants.CALLBACK_MEASURE:
995     case JmolConstants.CALLBACK_MESSAGE:
996     case JmolConstants.CALLBACK_PICK:
997     case JmolConstants.CALLBACK_SCRIPT:
998     case JmolConstants.CALLBACK_HOVER:
999     case JmolConstants.CALLBACK_ERROR:
1000       return true;
1001     case JmolConstants.CALLBACK_CLICK:
1002       case JmolConstants.CALLBACK_ANIMFRAME:
1003     case JmolConstants.CALLBACK_MINIMIZATION:
1004     case JmolConstants.CALLBACK_RESIZE:
1005     case JmolConstants.CALLBACK_SYNC:
1006     }
1007     return false;
1008   }
1009
1010   @Override
1011   public void setCallbackFunction(String callbackType,
1012           String callbackFunction)
1013   {
1014     System.err.println("Ignoring set-callback request to associate "+callbackType+" with function "+callbackFunction);
1015     
1016   }
1017
1018 }