7945fa7d7fe5e954b60f3823b394000e023d3020
[jalview.git] / src / jalview / gui / AppJmol.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.gui;
19
20 import java.util.regex.*;
21 import java.util.*;
22 import java.awt.*;
23 import javax.swing.*;
24 import javax.swing.event.*;
25 import java.awt.event.*;
26 import java.io.*;
27
28 import jalview.jbgui.GStructureViewer;
29 import jalview.bin.Cache;
30 import jalview.datamodel.*;
31 import jalview.gui.*;
32 import jalview.structure.*;
33 import jalview.datamodel.PDBEntry;
34 import jalview.io.*;
35 import jalview.schemes.*;
36 import jalview.ws.ebi.EBIFetchClient;
37
38 import org.jmol.api.*;
39 import org.jmol.adapter.smarter.SmarterJmolAdapter;
40 import org.jmol.popup.*;
41 import org.jmol.viewer.JmolConstants;
42
43 public class AppJmol extends GStructureViewer implements StructureListener,
44         JmolStatusListener, Runnable
45
46 {
47   JmolViewer viewer;
48
49   JmolPopup jmolpopup;
50
51   ScriptWindow scriptWindow;
52
53   PDBEntry pdbentry;
54
55   SequenceI[] sequence;
56
57   String[] chains;
58
59   StructureSelectionManager ssm;
60
61   JSplitPane splitPane;
62
63   RenderPanel renderPanel;
64
65   AlignmentPanel ap;
66
67   String fileLoadingError;
68
69   boolean colourBySequence = true;
70
71   boolean loadingFromArchive = false;
72
73   Vector atomsPicked = new Vector();
74
75   public AppJmol(String file, String id, SequenceI[] seq,
76           AlignmentPanel ap, String loadStatus, Rectangle bounds)
77   {
78     this(file, id, seq, ap, loadStatus, bounds, null);
79   }
80
81   public AppJmol(String file, String id, SequenceI[] seq,
82           AlignmentPanel ap, String loadStatus, Rectangle bounds,
83           String viewid)
84   {
85     loadingFromArchive = true;
86     pdbentry = new PDBEntry();
87     pdbentry.setFile(file);
88     pdbentry.setId(id);
89     this.sequence = seq;
90     this.ap = ap;
91     this.setBounds(bounds);
92     colourBySequence = false;
93     seqColour.setSelected(false);
94     viewId = viewid;
95     // jalview.gui.Desktop.addInternalFrame(this, "Loading File",
96     // bounds.width,bounds.height);
97
98     initJmol(loadStatus);
99
100     this.addInternalFrameListener(new InternalFrameAdapter()
101     {
102       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
103       {
104         closeViewer();
105       }
106     });
107   }
108
109   public synchronized void addSequence(SequenceI[] seq)
110   {
111     Vector v = new Vector();
112     for (int i = 0; i < sequence.length; i++)
113       v.addElement(sequence[i]);
114
115     for (int i = 0; i < seq.length; i++)
116       if (!v.contains(seq[i]))
117         v.addElement(seq[i]);
118
119     SequenceI[] tmp = new SequenceI[v.size()];
120     v.copyInto(tmp);
121     sequence = tmp;
122   }
123
124   public AppJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
125           AlignmentPanel ap)
126   {
127     // ////////////////////////////////
128     // Is the pdb file already loaded?
129     String alreadyMapped = StructureSelectionManager
130             .getStructureSelectionManager().alreadyMappedToFile(
131                     pdbentry.getId());
132
133     if (alreadyMapped != null)
134     {
135       int option = JOptionPane
136               .showInternalConfirmDialog(
137                       Desktop.desktop,
138                       pdbentry.getId()
139                               + " is already displayed."
140                               + "\nDo you want to map sequences to the visible structure?",
141                       "Map Sequences to Visible Window: "
142                               + pdbentry.getId(), JOptionPane.YES_NO_OPTION);
143
144       if (option == JOptionPane.YES_OPTION)
145       {
146         StructureSelectionManager.getStructureSelectionManager()
147                 .setMapping(seq, chains, alreadyMapped,
148                         AppletFormatAdapter.FILE);
149         if (ap.seqPanel.seqCanvas.fr != null)
150         {
151           ap.seqPanel.seqCanvas.fr.featuresAdded();
152           ap.paintAlignment(true);
153         }
154
155         // Now this AppJmol is mapped to new sequences. We must add them to
156         // the exisiting array
157         JInternalFrame[] frames = Desktop.instance.getAllFrames();
158
159         for (int i = 0; i < frames.length; i++)
160         {
161           if (frames[i] instanceof AppJmol)
162           {
163             AppJmol topJmol = ((AppJmol) frames[i]);
164             if (topJmol.pdbentry.getFile().equals(alreadyMapped))
165             {
166               topJmol.addSequence(seq);
167               break;
168             }
169           }
170         }
171
172         return;
173       }
174     }
175     // /////////////////////////////////
176
177     this.ap = ap;
178     this.pdbentry = pdbentry;
179     this.sequence = seq;
180     this.setSize(400, 400);
181     // jalview.gui.Desktop.addInternalFrame(this, "Jmol
182     // View"+(pdbentry.getId()!=null ? "for "+pdbentry.getId()
183     // : ""), 400, 400);
184
185     if (pdbentry.getFile() != null)
186     {
187       initJmol("load \"" + pdbentry.getFile() + "\"");
188     }
189     else
190     {
191       Thread worker = new Thread(this);
192       worker.start();
193     }
194
195     this.addInternalFrameListener(new InternalFrameAdapter()
196     {
197       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
198       {
199         closeViewer();
200       }
201     });
202   }
203
204   void initJmol(String command)
205   {
206     renderPanel = new RenderPanel();
207
208     this.getContentPane().add(renderPanel, java.awt.BorderLayout.CENTER);
209
210     StringBuffer title = new StringBuffer(sequence[0].getName() + ":"
211             + pdbentry.getId());
212
213     if (pdbentry.getProperty() != null)
214     {
215       if (pdbentry.getProperty().get("method") != null)
216       {
217         title.append(" Method: ");
218         title.append(pdbentry.getProperty().get("method"));
219       }
220       if (pdbentry.getProperty().get("chains") != null)
221       {
222         title.append(" Chain:");
223         title.append(pdbentry.getProperty().get("chains"));
224       }
225     }
226
227     this.setTitle(title.toString());
228     jalview.gui.Desktop.addInternalFrame(this, title.toString(),
229             getBounds().width, getBounds().height);
230     // * OK, but safer to assign htmlName, URL bases, comandOptions, and
231     // statusListener now.
232
233     viewer = org.jmol.api.JmolViewer.allocateViewer(renderPanel,
234             new SmarterJmolAdapter(), "", null, null, "", this);
235
236     jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
237
238     viewer.evalStringQuiet(command);
239   }
240
241   void setChainMenuItems(Vector chains)
242   {
243     chainMenu.removeAll();
244
245     JMenuItem menuItem = new JMenuItem("All");
246     menuItem.addActionListener(new ActionListener()
247     {
248       public void actionPerformed(ActionEvent evt)
249       {
250         allChainsSelected = true;
251         for (int i = 0; i < chainMenu.getItemCount(); i++)
252         {
253           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
254             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
255         }
256         centerViewer();
257         allChainsSelected = false;
258       }
259     });
260
261     chainMenu.add(menuItem);
262
263     for (int c = 0; c < chains.size(); c++)
264     {
265       menuItem = new JCheckBoxMenuItem(chains.elementAt(c).toString(), true);
266       menuItem.addItemListener(new ItemListener()
267       {
268         public void itemStateChanged(ItemEvent evt)
269         {
270           if (!allChainsSelected)
271             centerViewer();
272         }
273       });
274
275       chainMenu.add(menuItem);
276     }
277   }
278
279   boolean allChainsSelected = false;
280
281   void centerViewer()
282   {
283     StringBuffer cmd = new StringBuffer();
284     for (int i = 0; i < chainMenu.getItemCount(); i++)
285     {
286       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
287       {
288         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
289         if (item.isSelected())
290           cmd.append(":" + item.getText() + " or ");
291       }
292     }
293
294     if (cmd.length() > 0)
295       cmd.setLength(cmd.length() - 4);
296
297     viewer.evalStringQuiet("select *;restrict " + cmd + ";cartoon;center "
298             + cmd);
299   }
300
301   void closeViewer()
302   {
303     viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
304     viewer.evalStringQuiet("zap");
305     viewer.setJmolStatusListener(null);
306     viewer = null;
307
308     // We'll need to find out what other
309     // listeners need to be shut down in Jmol
310     StructureSelectionManager.getStructureSelectionManager()
311             .removeStructureViewerListener(this, pdbentry.getFile());
312   }
313
314   public void run()
315   {
316     try
317     {
318       // TODO: replace with reference fetching/transfer code (validate PDBentry
319       // as a DBRef?)
320       jalview.ws.dbsources.Pdb pdbclient = new jalview.ws.dbsources.Pdb();
321       AlignmentI pdbseq;
322       if ((pdbseq = pdbclient.getSequenceRecords(pdbentry.getId())) != null)
323       {
324         // just transfer the file name from the first seuqence's first PDBEntry
325         pdbentry.setFile(((PDBEntry) pdbseq.getSequenceAt(0).getPDBId()
326                 .elementAt(0)).getFile());
327         initJmol("load " + pdbentry.getFile());
328       }
329       else
330       {
331         JOptionPane
332                 .showInternalMessageDialog(
333                         Desktop.desktop,
334                         pdbentry.getId()
335                                 + " could not be retrieved. Please try downloading the file manually.",
336                         "Couldn't load file", JOptionPane.ERROR_MESSAGE);
337
338       }
339     } catch (OutOfMemoryError oomerror)
340     {
341       new OOMWarning("Retrieving PDB id " + pdbentry.getId() + " from MSD",
342               oomerror);
343     } catch (Exception ex)
344     {
345       ex.printStackTrace();
346     }
347   }
348
349   public void pdbFile_actionPerformed(ActionEvent actionEvent)
350   {
351     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache
352             .getProperty("LAST_DIRECTORY"));
353
354     chooser.setFileView(new JalviewFileView());
355     chooser.setDialogTitle("Save PDB File");
356     chooser.setToolTipText("Save");
357
358     int value = chooser.showSaveDialog(this);
359
360     if (value == JalviewFileChooser.APPROVE_OPTION)
361     {
362       try
363       {
364         BufferedReader in = new BufferedReader(new FileReader(pdbentry
365                 .getFile()));
366         File outFile = chooser.getSelectedFile();
367
368         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
369         String data;
370         while ((data = in.readLine()) != null)
371         {
372           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
373           {
374             out.println(data);
375           }
376         }
377         out.close();
378       } catch (Exception ex)
379       {
380         ex.printStackTrace();
381       }
382     }
383   }
384
385   public void viewMapping_actionPerformed(ActionEvent actionEvent)
386   {
387     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
388     jalview.gui.Desktop.addInternalFrame(cap, "PDB - Sequence Mapping",
389             550, 600);
390     cap.setText(StructureSelectionManager.getStructureSelectionManager()
391             .printMapping(pdbentry.getFile()));
392   }
393
394   /**
395    * DOCUMENT ME!
396    * 
397    * @param e
398    *          DOCUMENT ME!
399    */
400   public void eps_actionPerformed(ActionEvent e)
401   {
402     makePDBImage(jalview.util.ImageMaker.EPS);
403   }
404
405   /**
406    * DOCUMENT ME!
407    * 
408    * @param e
409    *          DOCUMENT ME!
410    */
411   public void png_actionPerformed(ActionEvent e)
412   {
413     makePDBImage(jalview.util.ImageMaker.PNG);
414   }
415
416   void makePDBImage(int type)
417   {
418     int width = getWidth();
419     int height = getHeight();
420
421     jalview.util.ImageMaker im;
422
423     if (type == jalview.util.ImageMaker.PNG)
424     {
425       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.PNG,
426               "Make PNG image from view", width, height, null, null);
427     }
428     else
429     {
430       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.EPS,
431               "Make EPS file from view", width, height, null, this
432                       .getTitle());
433     }
434
435     if (im.getGraphics() != null)
436     {
437       Rectangle rect = new Rectangle(width, height);
438       viewer.renderScreenImage(im.getGraphics(), rect.getSize(), rect);
439       im.writeImage();
440     }
441   }
442
443   public void seqColour_actionPerformed(ActionEvent actionEvent)
444   {
445     lastCommand = null;
446     colourBySequence = seqColour.isSelected();
447     colourBySequence(ap.alignFrame.alignPanel);
448   }
449
450   public void chainColour_actionPerformed(ActionEvent actionEvent)
451   {
452     colourBySequence = false;
453     seqColour.setSelected(false);
454     viewer.evalStringQuiet("select *;color chain");
455   }
456
457   public void chargeColour_actionPerformed(ActionEvent actionEvent)
458   {
459     colourBySequence = false;
460     seqColour.setSelected(false);
461     viewer.evalStringQuiet("select *;color white;select ASP,GLU;color red;"
462             + "select LYS,ARG;color blue;select CYS;color yellow");
463   }
464
465   public void zappoColour_actionPerformed(ActionEvent actionEvent)
466   {
467     setJalviewColourScheme(new ZappoColourScheme());
468   }
469
470   public void taylorColour_actionPerformed(ActionEvent actionEvent)
471   {
472     setJalviewColourScheme(new TaylorColourScheme());
473   }
474
475   public void hydroColour_actionPerformed(ActionEvent actionEvent)
476   {
477     setJalviewColourScheme(new HydrophobicColourScheme());
478   }
479
480   public void helixColour_actionPerformed(ActionEvent actionEvent)
481   {
482     setJalviewColourScheme(new HelixColourScheme());
483   }
484
485   public void strandColour_actionPerformed(ActionEvent actionEvent)
486   {
487     setJalviewColourScheme(new StrandColourScheme());
488   }
489
490   public void turnColour_actionPerformed(ActionEvent actionEvent)
491   {
492     setJalviewColourScheme(new TurnColourScheme());
493   }
494
495   public void buriedColour_actionPerformed(ActionEvent actionEvent)
496   {
497     setJalviewColourScheme(new BuriedColourScheme());
498   }
499
500   public void setJalviewColourScheme(ColourSchemeI cs)
501   {
502     colourBySequence = false;
503     seqColour.setSelected(false);
504
505     if (cs == null)
506       return;
507
508     String res;
509     int index;
510     Color col;
511
512     Enumeration en = ResidueProperties.aa3Hash.keys();
513     StringBuffer command = new StringBuffer("select *;color white;");
514     while (en.hasMoreElements())
515     {
516       res = en.nextElement().toString();
517       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
518       if (index > 20)
519         continue;
520
521       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
522
523       command.append("select " + res + ";color[" + col.getRed() + ","
524               + col.getGreen() + "," + col.getBlue() + "];");
525     }
526
527     viewer.evalStringQuiet(command.toString());
528   }
529
530   public void userColour_actionPerformed(ActionEvent actionEvent)
531   {
532     new UserDefinedColours(this, null);
533   }
534
535   public void backGround_actionPerformed(ActionEvent actionEvent)
536   {
537     java.awt.Color col = JColorChooser.showDialog(this,
538             "Select Background Colour", null);
539
540     if (col != null)
541     {
542       viewer.evalStringQuiet("background [" + col.getRed() + ","
543               + col.getGreen() + "," + col.getBlue() + "];");
544     }
545   }
546
547   public void jmolHelp_actionPerformed(ActionEvent actionEvent)
548   {
549     try
550     {
551       jalview.util.BrowserLauncher
552               .openURL("http://jmol.sourceforge.net/docs/JmolUserGuide/");
553     } catch (Exception ex)
554     {
555     }
556   }
557
558   // ////////////////////////////////
559   // /StructureListener
560   public String getPdbFile()
561   {
562     return pdbentry.getFile();
563   }
564
565   Pattern pattern = Pattern
566           .compile("\\[(.*)\\]([0-9]+)(:[a-zA-Z]*)?\\.([a-zA-Z]+)(/[0-9]*)?");
567
568   String lastMessage;
569
570   public void mouseOverStructure(int atomIndex, String strInfo)
571   {
572     Matcher matcher = pattern.matcher(strInfo);
573     matcher.find();
574     matcher.group(1);
575     int pdbResNum = Integer.parseInt(matcher.group(2));
576     String chainId = matcher.group(3);
577
578     if (chainId != null)
579       chainId = chainId.substring(1, chainId.length());
580     else
581     {
582       chainId = " ";
583     }
584
585     if (lastMessage == null || !lastMessage.equals(strInfo))
586     {
587       ssm.mouseOverStructure(pdbResNum, chainId, pdbentry.getFile());
588     }
589     lastMessage = strInfo;
590   }
591
592   StringBuffer resetLastRes = new StringBuffer();
593
594   StringBuffer eval = new StringBuffer();
595
596   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
597           String pdbfile)
598   {
599     // TODO: rna: remove CA dependency in select string
600     if (!pdbfile.equals(pdbentry.getFile()))
601       return;
602
603     if (resetLastRes.length() > 0)
604     {
605       viewer.evalStringQuiet(resetLastRes.toString());
606     }
607
608     eval.setLength(0);
609     eval.append("select " + pdbResNum);
610
611     resetLastRes.setLength(0);
612     resetLastRes.append("select " + pdbResNum);
613
614     eval.append(":");
615     resetLastRes.append(":");
616     if (!chain.equals(" "))
617     {
618       eval.append(chain);
619       resetLastRes.append(chain);
620     }
621
622     eval.append(";wireframe 100;" + eval.toString() + " and not hetero;"); // ".*;");
623
624     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
625     // + ".*;spacefill 0;");
626             + " and not hetero;spacefill 0;");
627
628     eval.append("spacefill 200;select none");
629     // System.out.println("jmol:\n"+eval+"\n");
630     viewer.evalStringQuiet(eval.toString());
631   }
632
633   public Color getColour(int atomIndex, int pdbResNum, String chain,
634           String pdbfile)
635   {
636     if (!pdbfile.equals(pdbentry.getFile()))
637       return null;
638
639     return new Color(viewer.getAtomArgb(atomIndex));
640   }
641
642   public void updateColours(Object source)
643   {
644     colourBySequence((AlignmentPanel) source);
645   }
646
647   // End StructureListener
648   // //////////////////////////
649
650   String lastCommand;
651
652   FeatureRenderer fr = null;
653
654   public void colourBySequence(AlignmentPanel sourceap)
655   {
656     this.ap = sourceap;
657
658     if (!colourBySequence || ap.alignFrame.getCurrentView() != ap.av)
659       return;
660
661     StructureMapping[] mapping = ssm.getMapping(pdbentry.getFile());
662
663     if (mapping.length < 1)
664       return;
665
666     SequenceRenderer sr = new SequenceRenderer(ap.av);
667
668     boolean showFeatures = false;
669
670     if (ap.av.showSequenceFeatures)
671     {
672       showFeatures = true;
673       if (fr == null)
674       {
675         fr = new jalview.gui.FeatureRenderer(ap);
676       }
677
678       fr.transferSettings(ap.seqPanel.seqCanvas.getFeatureRenderer());
679     }
680
681     StringBuffer command = new StringBuffer();
682
683     int lastPos = -1;
684     for (int sp, s = 0; s < sequence.length; s++)
685     {
686       for (int m = 0; m < mapping.length; m++)
687       {
688         if (mapping[m].getSequence() == sequence[s]
689                 && (sp = ap.av.alignment.findIndex(sequence[s])) > -1)
690         {
691           SequenceI asp = ap.av.alignment.getSequenceAt(sp);
692           for (int r = 0; r < asp.getLength(); r++)
693           {
694             // No mapping to gaps in sequence.
695             if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
696             {
697               continue;
698             }
699             int pos = mapping[m].getPDBResNum(asp.findPosition(r));
700
701             if (pos < 1 || pos == lastPos)
702               continue;
703
704             lastPos = pos;
705
706             Color col = sr.getResidueBoxColour(asp, r);
707
708             if (showFeatures)
709               col = fr.findFeatureColour(col, asp, r);
710
711             if (command.toString().endsWith(
712                     ":" + mapping[m].getChain() + ";color[" + col.getRed()
713                             + "," + col.getGreen() + "," + col.getBlue()
714                             + "]"))
715             {
716               command = condenseCommand(command, pos);
717               continue;
718             }
719
720             command.append(";select " + pos);
721
722             if (!mapping[m].getChain().equals(" "))
723             {
724               command.append(":" + mapping[m].getChain());
725             }
726
727             command.append(";color[" + col.getRed() + "," + col.getGreen()
728                     + "," + col.getBlue() + "]");
729
730           }
731           break;
732         }
733       }
734     }
735
736     if (lastCommand == null || !lastCommand.equals(command.toString()))
737     {
738       viewer.evalStringQuiet(command.toString());
739     }
740     lastCommand = command.toString();
741   }
742
743   StringBuffer condenseCommand(StringBuffer command, int pos)
744   {
745     StringBuffer sb = new StringBuffer(command.substring(0, command
746             .lastIndexOf("select") + 7));
747
748     command.delete(0, sb.length());
749
750     String start;
751
752     if (command.indexOf("-") > -1)
753     {
754       start = command.substring(0, command.indexOf("-"));
755     }
756     else
757     {
758       start = command.substring(0, command.indexOf(":"));
759     }
760
761     sb.append(start + "-" + pos + command.substring(command.indexOf(":")));
762
763     return sb;
764   }
765
766   // ///////////////////////////////
767   // JmolStatusListener
768
769   public String eval(String strEval)
770   {
771     // System.out.println(strEval);
772     // "# 'eval' is implemented only for the applet.";
773     return null;
774   }
775
776   public void createImage(String file, String type, int quality)
777   {
778     System.out.println("JMOL CREATE IMAGE");
779   }
780
781   public void notifyFileLoaded(String fullPathName, String fileName,
782           String modelName, String errorMsg, int modelParts)
783   {
784     if (errorMsg != null)
785     {
786       fileLoadingError = errorMsg;
787       repaint();
788       return;
789     }
790
791     fileLoadingError = null;
792
793     if (fileName != null)
794     {
795       // TODO: do some checking using the modelPts number of parts against our own estimate of the number of chains
796       // FILE LOADED OK
797       ssm = StructureSelectionManager.getStructureSelectionManager();
798       MCview.PDBfile pdbFile = ssm.setMapping(sequence, chains, pdbentry
799               .getFile(), AppletFormatAdapter.FILE);
800       ssm.addStructureViewerListener(this);
801       Vector chains = new Vector();
802       for (int i = 0; i < pdbFile.chains.size(); i++)
803       {
804         chains
805                 .addElement(((MCview.PDBChain) pdbFile.chains.elementAt(i)).id);
806       }
807       setChainMenuItems(chains);
808
809       jmolpopup.updateComputedMenus();
810
811       if (!loadingFromArchive)
812       {
813         viewer
814                 .evalStringQuiet("select backbone;restrict;cartoon;wireframe off;spacefill off");
815
816         colourBySequence(ap);
817       }
818       if (fr != null)
819         fr.featuresAdded();
820
821       loadingFromArchive = false;
822     }
823     else
824       return;
825   }
826
827   public void sendConsoleEcho(String strEcho)
828   {
829     if (scriptWindow != null)
830       scriptWindow.sendConsoleEcho(strEcho);
831   }
832
833   public void sendConsoleMessage(String strStatus)
834   {
835     if (scriptWindow != null)
836       scriptWindow.sendConsoleMessage(strStatus);
837   }
838
839   public void notifyScriptTermination(String strStatus, int msWalltime)
840   {
841     if (scriptWindow != null)
842       scriptWindow.notifyScriptTermination(strStatus, msWalltime);
843   }
844
845   public void handlePopupMenu(int x, int y)
846   {
847     jmolpopup.show(x, y);
848   }
849
850   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
851   {
852     notifyAtomPicked(iatom, strMeasure, null);
853   }
854
855   public void notifyAtomPicked(int atomIndex, String strInfo, String strData)
856   {
857     if (strData!=null)
858     {
859       Cache.log.info("Non null pick data string: "+strData+" (other info: '"+strInfo+"' pos "+atomIndex+")");
860     }
861     Matcher matcher = pattern.matcher(strInfo);
862     matcher.find();
863
864     matcher.group(1);
865     String resnum = new String(matcher.group(2));
866     String chainId = matcher.group(3);
867
868     String picked = resnum;
869
870     if (chainId != null)
871       picked += (":" + chainId.substring(1, chainId.length()));
872
873     picked = "(("+picked+".CA" + ")|("+picked+".P"+"))";
874
875     if (!atomsPicked.contains(picked))
876     {
877       if (chainId != null)
878         viewer.evalString("select " + picked + ";label %n %r:%c");
879       else
880         viewer.evalString("select " + picked + ";label %n %r");
881       atomsPicked.addElement(picked);
882     }
883     else
884     {
885       viewer.evalString("select " + picked + ";label off");
886       atomsPicked.removeElement(picked);
887     }
888
889     if (scriptWindow != null)
890     {
891       scriptWindow.sendConsoleMessage(strInfo);
892       scriptWindow.sendConsoleMessage("\n");
893     }
894   }
895
896   public void notifyAtomHovered(int atomIndex, String strInfo, String data)
897   {
898     if (data!=null)
899     {
900       Cache.log.info("Non null hover data string: "+data+" (other info: '"+strInfo+"' pos "+atomIndex+")");
901     }
902     mouseOverStructure(atomIndex, strInfo);
903   }
904
905   @Override
906   public void showUrl(String url)
907   {
908     try {
909       jalview.util.BrowserLauncher.openURL(url);
910     } catch (IOException e)
911     {
912       Cache.log.error("Failed to launch Jmol-associated url "+url,e);
913       // TODO: 2.6 : warn user if browser was not configured.
914     }
915   }
916
917   public void showConsole(boolean showConsole)
918   {
919     if (scriptWindow == null)
920       scriptWindow = new ScriptWindow(this);
921
922     if (showConsole)
923     {
924       if (splitPane == null)
925       {
926         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
927         splitPane.setTopComponent(renderPanel);
928         splitPane.setBottomComponent(scriptWindow);
929         this.getContentPane().add(splitPane, BorderLayout.CENTER);
930       }
931
932       splitPane.setDividerLocation(getHeight() - 200);
933       splitPane.validate();
934     }
935     else
936     {
937       if (splitPane != null)
938         splitPane.setVisible(false);
939
940       splitPane = null;
941
942       this.getContentPane().add(renderPanel, BorderLayout.CENTER);
943     }
944
945     validate();
946   }
947
948   public float[][] functionXY(String functionName, int x, int y)
949   {
950     return null;
951   }
952
953   // /End JmolStatusListener
954   // /////////////////////////////
955
956   class RenderPanel extends JPanel
957   {
958     final Dimension currentSize = new Dimension();
959
960     final Rectangle rectClip = new Rectangle();
961
962     public void paintComponent(Graphics g)
963     {
964       getSize(currentSize);
965       g.getClipBounds(rectClip);
966
967       if (viewer == null)
968       {
969         g.setColor(Color.black);
970         g.fillRect(0, 0, currentSize.width, currentSize.height);
971         g.setColor(Color.white);
972         g.setFont(new Font("Verdana", Font.BOLD, 14));
973         g.drawString("Retrieving PDB data....", 20, currentSize.height / 2);
974       }
975       else if (fileLoadingError != null)
976       {
977         g.setColor(Color.black);
978         g.fillRect(0, 0, currentSize.width, currentSize.height);
979         g.setColor(Color.white);
980         g.setFont(new Font("Verdana", Font.BOLD, 14));
981         g.drawString("Error loading file..." + pdbentry.getId(), 20,
982                 currentSize.height / 2);
983       }
984       else
985       {
986         viewer.renderScreenImage(g, currentSize, rectClip);
987       }
988     }
989   }
990
991   String viewId = null;
992
993   public String getViewId()
994   {
995     if (viewId == null)
996     {
997       viewId = System.currentTimeMillis() + "." + this.hashCode();
998     }
999     return viewId;
1000   }
1001
1002   @Override
1003   public String createImage(String fileName, String type,
1004           Object textOrBytes, int quality)
1005   {
1006     // TODO Auto-generated method stub
1007     return null;
1008   }
1009
1010   @Override
1011   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
1012   {
1013     // TODO Auto-generated method stub
1014     return null;
1015   }
1016
1017   @Override
1018   public Hashtable getRegistryInfo()
1019   {
1020     // TODO Auto-generated method stub
1021     return null;
1022   }
1023
1024   @Override
1025   public void notifyCallback(int type, Object[] data)
1026   {
1027     try {
1028     switch (type)
1029     {
1030     case JmolConstants.CALLBACK_LOADSTRUCT:
1031       notifyFileLoaded((String) data[1], (String) data[2], 
1032               (String) data[3], (String) data[4], ((Integer) data[5]).intValue());
1033               
1034       break;
1035     case JmolConstants.CALLBACK_PICK:
1036       notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1], (String) data[0]);
1037       // also highlight in alignment
1038     case JmolConstants.CALLBACK_HOVER:
1039       notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1], (String) data[0]);
1040       break;
1041     case JmolConstants.CALLBACK_SCRIPT:
1042       notifyScriptTermination((String)data[2], ((Integer)data[3]).intValue());
1043       break;
1044     case JmolConstants.CALLBACK_ECHO:
1045       sendConsoleEcho((String)data[1]);
1046       break;
1047     case JmolConstants.CALLBACK_MESSAGE:
1048       sendConsoleMessage((data==null) ? ((String) null) : (String)data[1]);
1049       break;
1050     case JmolConstants.CALLBACK_MEASURE:
1051     case JmolConstants.CALLBACK_CLICK:
1052       default:
1053         System.err.println("Unhandled callback "+type+" "+data);
1054         break;
1055     }
1056     }
1057     catch (Exception e)
1058     {
1059       Cache.log.warn("Squashed Jmol callback handler error: ",e);
1060     }
1061   }
1062
1063   @Override
1064   public boolean notifyEnabled(int callbackPick)
1065   {
1066     switch (callbackPick)
1067     {
1068     case JmolConstants.CALLBACK_ECHO:
1069     case JmolConstants.CALLBACK_LOADSTRUCT:
1070     case JmolConstants.CALLBACK_MEASURE:
1071     case JmolConstants.CALLBACK_MESSAGE:
1072     case JmolConstants.CALLBACK_PICK:
1073     case JmolConstants.CALLBACK_SCRIPT:
1074     case JmolConstants.CALLBACK_HOVER:
1075     case JmolConstants.CALLBACK_ERROR:
1076       return true;
1077     case JmolConstants.CALLBACK_CLICK:
1078       case JmolConstants.CALLBACK_ANIMFRAME:
1079     case JmolConstants.CALLBACK_MINIMIZATION:
1080     case JmolConstants.CALLBACK_RESIZE:
1081     case JmolConstants.CALLBACK_SYNC:
1082     }
1083     return false;
1084   }
1085
1086   @Override
1087   public void setCallbackFunction(String callbackType,
1088           String callbackFunction)
1089   {
1090     Cache.log.debug("Ignoring set-callback request to associate "+callbackType+" with function "+callbackFunction);
1091     
1092   }
1093
1094 }