revised sequence/structure binding so one structure associated with x-seuqenceI's...
[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 Runnable,
45         SequenceStructureBinding
46
47 {
48   AppJmolBinding jmb;
49
50   ScriptWindow scriptWindow;
51
52   JSplitPane splitPane;
53
54   RenderPanel renderPanel;
55
56   AlignmentPanel ap;
57
58   Vector atomsPicked = new Vector();
59
60   public AppJmol(String file, String id, SequenceI[] seq,
61           AlignmentPanel ap, String loadStatus, Rectangle bounds)
62   {
63     this(file, id, seq, ap, loadStatus, bounds, null);
64   }
65
66   public AppJmol(String file, String id, SequenceI[] seq,
67           AlignmentPanel ap, String loadStatus, Rectangle bounds,
68           String viewid)
69   {
70     PDBEntry pdbentry = new PDBEntry();
71     pdbentry.setFile(file);
72     pdbentry.setId(id);
73     // / TODO: check if protocol is needed to be set, and if chains are
74     // autodiscovered.
75     jmb = new AppJmolBinding(this, new PDBEntry[]
76     { pdbentry }, new SequenceI[][] { seq }, null, null);
77
78     jmb.setLoadingFromArchive(true);
79     this.ap = ap;
80     this.setBounds(bounds);
81     jmb.setColourBySequence(false);
82     seqColour.setSelected(false);
83     viewId = viewid;
84     // jalview.gui.Desktop.addInternalFrame(this, "Loading File",
85     // bounds.width,bounds.height);
86
87     this.addInternalFrameListener(new InternalFrameAdapter()
88     {
89       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
90       {
91         closeViewer();
92       }
93     });
94     initJmol(loadStatus); // pdbentry, seq, JBPCHECK!
95
96   }
97
98   public AppJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
99           AlignmentPanel ap)
100   {
101     // ////////////////////////////////
102     // Is the pdb file already loaded?
103     String alreadyMapped = StructureSelectionManager
104             .getStructureSelectionManager().alreadyMappedToFile(
105                     pdbentry.getId());
106
107     if (alreadyMapped != null)
108     {
109       int option = JOptionPane
110               .showInternalConfirmDialog(
111                       Desktop.desktop,
112                       pdbentry.getId()
113                               + " is already displayed."
114                               + "\nDo you want to re-use this viewer ?",
115                       "Map Sequences to Visible Window: "
116                               + pdbentry.getId(), JOptionPane.YES_NO_OPTION);
117
118       if (option == JOptionPane.YES_OPTION)
119       {
120         StructureSelectionManager.getStructureSelectionManager()
121                 .setMapping(seq, chains, alreadyMapped,
122                         AppletFormatAdapter.FILE);
123         if (ap.seqPanel.seqCanvas.fr != null)
124         {
125           ap.seqPanel.seqCanvas.fr.featuresAdded();
126           ap.paintAlignment(true);
127         }
128
129         // Now this AppJmol is mapped to new sequences. We must add them to
130         // the exisiting array
131         JInternalFrame[] frames = Desktop.instance.getAllFrames();
132
133         for (int i = 0; i < frames.length; i++)
134         {
135           if (frames[i] instanceof AppJmol)
136           {
137             AppJmol topJmol = ((AppJmol) frames[i]);
138             // JBPNOTE: this looks like a binding routine, rather than a gui
139             // routine
140             for (int pe = 0; pe < topJmol.jmb.pdbentry.length; pe++)
141             {
142               if (topJmol.jmb.pdbentry[pe].getFile().equals(alreadyMapped))
143               {
144                 topJmol.jmb.addSequence(pe, seq);
145                 break;
146               }
147             }
148           }
149         }
150
151         return;
152       }
153     }
154     // /////////////////////////////////
155     // Check if there are other Jmol views involving this alignment
156     // and prompt user about adding this molecule to one of them
157     Vector existingViews = getJmolsFor(ap);
158     if (existingViews.size()>0)
159     {
160       Enumeration jm = existingViews.elements();
161       while (jm.hasMoreElements()) {
162         AppJmol topJmol = (AppJmol) jm.nextElement();
163         // TODO: highlight topJmol in view somehow
164         int option = JOptionPane
165         .showInternalConfirmDialog(
166               Desktop.desktop, "Do you want to add "+
167               pdbentry.getId()+" to the view called\n'"+topJmol.getTitle()
168                       + "'\n",
169               "Align to existing structure view"
170                       , JOptionPane.YES_NO_OPTION);
171         if (option == JOptionPane.YES_OPTION)
172         {
173           topJmol.jmb.addStructure(pdbentry, seq, chains,true);
174           return;
175         }
176       }
177     }
178     // /////////////////////////////////
179
180     jmb = new AppJmolBinding(this, new PDBEntry[]
181     { pdbentry }, new SequenceI[][]{seq}, null, null);
182     this.ap = ap;
183     setSize(400, 400); // probably should be a configurable/dynamic default here
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
205   private Vector getJmolsFor(AlignmentPanel ap2)
206   {
207     Vector otherJmols= new Vector();
208     // Now this AppJmol is mapped to new sequences. We must add them to
209     // the exisiting array
210     JInternalFrame[] frames = Desktop.instance.getAllFrames();
211
212     for (int i = 0; i < frames.length; i++)
213     {
214       if (frames[i] instanceof AppJmol)
215       {
216         AppJmol topJmol = ((AppJmol) frames[i]);
217         if (topJmol.ap==ap2)
218         {
219           otherJmols.addElement(topJmol);
220         }
221       }
222     }
223     return otherJmols;
224   }
225
226   void initJmol(String command)
227   {
228     jmb.setFinishedInit(false);
229     renderPanel = new RenderPanel();
230     // TODO: consider waiting until the structure/view is fully loaded before
231     // displaying
232     this.getContentPane().add(renderPanel, java.awt.BorderLayout.CENTER);
233     jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(),
234             getBounds().width, getBounds().height);
235     jmb.allocateViewer(renderPanel, true, "", null, null, "");
236     jmb.newJmolPopup(true, "Jmol", true);
237     jmb.evalStateCommand(command);
238     jmb.setFinishedInit(true);
239   }
240
241   void setChainMenuItems(Vector chains)
242   {
243     chainMenu.removeAll();
244     if (chains==null)
245     {
246       return;
247     }
248     JMenuItem menuItem = new JMenuItem("All");
249     menuItem.addActionListener(new ActionListener()
250     {
251       public void actionPerformed(ActionEvent evt)
252       {
253         allChainsSelected = true;
254         for (int i = 0; i < chainMenu.getItemCount(); i++)
255         {
256           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
257             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
258         }
259         centerViewer();
260         allChainsSelected = false;
261       }
262     });
263
264     chainMenu.add(menuItem);
265
266     for (int c = 0; c < chains.size(); c++)
267     {
268       menuItem = new JCheckBoxMenuItem(chains.elementAt(c).toString(), true);
269       menuItem.addItemListener(new ItemListener()
270       {
271         public void itemStateChanged(ItemEvent evt)
272         {
273           if (!allChainsSelected)
274             centerViewer();
275         }
276       });
277
278       chainMenu.add(menuItem);
279     }
280   }
281
282   boolean allChainsSelected = false;
283
284   void centerViewer()
285   {
286     Vector toshow = new Vector();
287     String lbl;
288     int mlength, p, mnum;
289     for (int i = 0; i < chainMenu.getItemCount(); i++)
290     {
291       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
292       {
293         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
294         if (item.isSelected())
295         {
296           toshow.addElement(item.getText());
297         }
298       }
299     }
300     jmb.centerViewer(toshow);
301   }
302
303   void closeViewer()
304   {
305     jmb.closeViewer();
306     // TODO: check for memory leaks where instance isn't finalised because jmb
307     // holds a reference to the window
308     jmb = null;
309   }
310
311   public void run()
312   {
313     String pdbid = "";
314     // todo - record which pdbids were successfuly imported.
315     StringBuffer errormsgs = new StringBuffer(), files = new StringBuffer();
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       for (int pi = 0; pi < jmb.pdbentry.length; pi++)
322       {
323         AlignmentI pdbseq;
324         if ((pdbseq = pdbclient.getSequenceRecords(pdbid = jmb.pdbentry[pi]
325                 .getId())) != null)
326         {
327           String file;
328           // just transfer the file name from the first sequence's first
329           // PDBEntry
330           jmb.pdbentry[pi].setFile(file = ((PDBEntry) pdbseq.getSequenceAt(
331                   0).getPDBId().elementAt(0)).getFile());
332           files.append("\"" + file + "\"");
333         }
334         else
335         {
336           errormsgs.append("'" + pdbid + "' ");
337         }
338       }
339     } catch (OutOfMemoryError oomerror)
340     {
341       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
342     } catch (Exception ex)
343     {
344       ex.printStackTrace();
345       errormsgs.append("'" + pdbid + "'");
346     }
347     if (errormsgs.length() > 0)
348     {
349
350       JOptionPane.showInternalMessageDialog(Desktop.desktop,
351               "The following pdb entries could not be retrieved from the PDB:\n"
352                       + errormsgs.toString()
353                       + "\nPlease try downloading them manually.",
354               "Couldn't load file", JOptionPane.ERROR_MESSAGE);
355
356     }
357     if (files.length() > 0)
358     {
359       try
360       {
361         initJmol("load FILES " + files.toString());
362       } catch (OutOfMemoryError oomerror)
363       {
364         new OOMWarning("When trying to open the Jmol viewer!", oomerror);
365         Cache.log.debug("File locations are " + files);
366       } catch (Exception ex)
367       {
368         Cache.log.error("Couldn't open Jmol viewer!", ex);
369       }
370     }
371   }
372
373   public void pdbFile_actionPerformed(ActionEvent actionEvent)
374   {
375     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache
376             .getProperty("LAST_DIRECTORY"));
377
378     chooser.setFileView(new JalviewFileView());
379     chooser.setDialogTitle("Save PDB File");
380     chooser.setToolTipText("Save");
381
382     int value = chooser.showSaveDialog(this);
383
384     if (value == JalviewFileChooser.APPROVE_OPTION)
385     {
386       try
387       {
388         // TODO: cope with multiple PDB files in view 
389         BufferedReader in = new BufferedReader(new FileReader(jmb.getPdbFile()[0]));
390         File outFile = chooser.getSelectedFile();
391
392         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
393         String data;
394         while ((data = in.readLine()) != null)
395         {
396           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
397           {
398             out.println(data);
399           }
400         }
401         out.close();
402       } catch (Exception ex)
403       {
404         ex.printStackTrace();
405       }
406     }
407   }
408
409   public void viewMapping_actionPerformed(ActionEvent actionEvent)
410   {
411     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
412     try {for (int pdbe = 0; pdbe<jmb.pdbentry.length; pdbe++) {
413       cap.appendText(StructureSelectionManager.getStructureSelectionManager()
414             .printMapping(jmb.pdbentry[pdbe].getFile()));
415       cap.appendText("\n");
416     }} catch (OutOfMemoryError e)
417     {
418       new OOMWarning("composing sequence-structure alignments for display in text box.", e);
419       cap.dispose();
420       return;
421     }
422     jalview.gui.Desktop.addInternalFrame(cap, "PDB - Sequence Mapping",
423             550, 600);
424   }
425
426   /**
427    * DOCUMENT ME!
428    * 
429    * @param e
430    *          DOCUMENT ME!
431    */
432   public void eps_actionPerformed(ActionEvent e)
433   {
434     makePDBImage(jalview.util.ImageMaker.EPS);
435   }
436
437   /**
438    * DOCUMENT ME!
439    * 
440    * @param e
441    *          DOCUMENT ME!
442    */
443   public void png_actionPerformed(ActionEvent e)
444   {
445     makePDBImage(jalview.util.ImageMaker.PNG);
446   }
447
448   void makePDBImage(int type)
449   {
450     int width = getWidth();
451     int height = getHeight();
452
453     jalview.util.ImageMaker im;
454
455     if (type == jalview.util.ImageMaker.PNG)
456     {
457       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.PNG,
458               "Make PNG image from view", width, height, null, null);
459     }
460     else
461     {
462       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.EPS,
463               "Make EPS file from view", width, height, null, this
464                       .getTitle());
465     }
466
467     if (im.getGraphics() != null)
468     {
469       Rectangle rect = new Rectangle(width, height);
470       jmb.viewer.renderScreenImage(im.getGraphics(), rect.getSize(), rect);
471       im.writeImage();
472     }
473   }
474
475   public void seqColour_actionPerformed(ActionEvent actionEvent)
476   {
477     jmb.setColourBySequence(seqColour.isSelected());
478     // Set the colour using the current view for the associated alignframe
479     jmb.colourBySequence(ap.alignFrame.viewport.showSequenceFeatures, ap.alignFrame.viewport.alignment);
480   }
481
482   public void chainColour_actionPerformed(ActionEvent actionEvent)
483   {
484     chainColour.setSelected(true);
485     jmb.colourByChain();
486   }
487
488   public void chargeColour_actionPerformed(ActionEvent actionEvent)
489   {
490     chargeColour.setSelected(true);
491     jmb.colourByCharge();
492   }
493
494   public void zappoColour_actionPerformed(ActionEvent actionEvent)
495   {
496     zappoColour.setSelected(true);
497     jmb.setJalviewColourScheme(new ZappoColourScheme());
498   }
499
500   public void taylorColour_actionPerformed(ActionEvent actionEvent)
501   {
502     taylorColour.setSelected(true);
503     jmb.setJalviewColourScheme(new TaylorColourScheme());
504   }
505
506   public void hydroColour_actionPerformed(ActionEvent actionEvent)
507   {
508     hydroColour.setSelected(true);
509     jmb.setJalviewColourScheme(new HydrophobicColourScheme());
510   }
511
512   public void helixColour_actionPerformed(ActionEvent actionEvent)
513   {
514     helixColour.setSelected(true);
515     jmb.setJalviewColourScheme(new HelixColourScheme());
516   }
517
518   public void strandColour_actionPerformed(ActionEvent actionEvent)
519   {
520     strandColour.setSelected(true);
521     jmb.setJalviewColourScheme(new StrandColourScheme());
522   }
523
524   public void turnColour_actionPerformed(ActionEvent actionEvent)
525   {
526     turnColour.setSelected(true);
527     jmb.setJalviewColourScheme(new TurnColourScheme());
528   }
529
530   public void buriedColour_actionPerformed(ActionEvent actionEvent)
531   {
532     buriedColour.setSelected(true);
533     jmb.setJalviewColourScheme(new BuriedColourScheme());
534   }
535
536
537   public void userColour_actionPerformed(ActionEvent actionEvent)
538   {
539     userColour.setSelected(true);
540     new UserDefinedColours(this, null);
541   }
542
543   public void backGround_actionPerformed(ActionEvent actionEvent)
544   {
545     java.awt.Color col = JColorChooser.showDialog(this,
546             "Select Background Colour", null);
547     if (col != null)
548     {
549       jmb.setBackgroundColour(col);
550     }
551   }
552
553   public void jmolHelp_actionPerformed(ActionEvent actionEvent)
554   {
555     try
556     {
557       jalview.util.BrowserLauncher
558               .openURL("http://jmol.sourceforge.net/docs/JmolUserGuide/");
559     } catch (Exception ex)
560     {
561     }
562   }
563
564   public void showConsole(boolean showConsole)
565   {
566     if (scriptWindow == null)
567       scriptWindow = new ScriptWindow(this);
568
569     if (showConsole)
570     {
571       if (splitPane == null)
572       {
573         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
574         splitPane.setTopComponent(renderPanel);
575         splitPane.setBottomComponent(scriptWindow);
576         this.getContentPane().add(splitPane, BorderLayout.CENTER);
577       }
578
579       splitPane.setDividerLocation(getHeight() - 200);
580       splitPane.validate();
581     }
582     else
583     {
584       if (splitPane != null)
585         splitPane.setVisible(false);
586
587       splitPane = null;
588
589       this.getContentPane().add(renderPanel, BorderLayout.CENTER);
590     }
591
592     validate();
593   }
594
595   class RenderPanel extends JPanel
596   {
597     final Dimension currentSize = new Dimension();
598
599     final Rectangle rectClip = new Rectangle();
600
601     public void paintComponent(Graphics g)
602     {
603       getSize(currentSize);
604       g.getClipBounds(rectClip);
605
606       if (jmb.fileLoadingError != null)
607       {
608         g.setColor(Color.black);
609         g.fillRect(0, 0, currentSize.width, currentSize.height);
610         g.setColor(Color.white);
611         g.setFont(new Font("Verdana", Font.BOLD, 14));
612         g.drawString("Error loading file...", 20,
613                 currentSize.height / 2);
614         StringBuffer sb = new StringBuffer();
615         int lines=0;
616         for (int e=0;e<jmb.pdbentry.length; e++)
617         {
618           sb.append(jmb.pdbentry[e].getId());
619           if (e<jmb.pdbentry.length-1) {
620             sb.append(",");
621           }
622
623           if (e==jmb.pdbentry.length-1 || sb.length()>20)
624           {
625             lines++;
626             g.drawString(sb.toString(), 20, currentSize.height/2 - lines*g.getFontMetrics().getHeight());
627           }
628         }
629       }
630       else
631         if (jmb == null || jmb.viewer == null || !jmb.isFinishedInit())
632         {
633           g.setColor(Color.black);
634           g.fillRect(0, 0, currentSize.width, currentSize.height);
635           g.setColor(Color.white);
636           g.setFont(new Font("Verdana", Font.BOLD, 14));
637           g.drawString("Retrieving PDB data....", 20, currentSize.height / 2);
638         }
639         else 
640       {
641         jmb.viewer.renderScreenImage(g, currentSize, rectClip);
642       }
643     }
644   }
645
646   String viewId = null;
647
648   public String getViewId()
649   {
650     if (viewId == null)
651     {
652       viewId = System.currentTimeMillis() + "." + this.hashCode();
653     }
654     return viewId;
655   }
656   public void updateTitleAndMenus()
657   {
658     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
659     {
660       repaint();
661       return;
662     }
663     setChainMenuItems(jmb.chainNames);
664     jmb.colourBySequence(ap.av.getShowSequenceFeatures(), ap.av.alignment);
665     
666     this.setTitle(jmb.getViewerTitle());
667     if (jmb.getPdbFile().length>1 && jmb.sequence.length>1) {
668        jmolActionMenu.setVisible(true);
669     }
670   }
671
672   /* (non-Javadoc)
673    * @see jalview.jbgui.GStructureViewer#alignStructs_actionPerformed(java.awt.event.ActionEvent)
674    */
675   @Override
676   protected void alignStructs_actionPerformed(ActionEvent actionEvent)
677   {
678     
679     try {
680       jmb.superposeStructures(ap.av.getAlignment(), -1, ap.av.getColumnSelection());
681     } catch (Exception e)
682     {
683       Cache.log.info("Couldn't align structures in alignframe "+ap.alignFrame.getTitle(),e);
684       
685     }
686   }
687
688   public void setJalviewColourScheme(ColourSchemeI ucs)
689   {
690     jmb.setJalviewColourScheme(ucs);
691     
692   }
693
694 }