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