9e1e35aee0bc9639e3e2a336b4cd0026282004ff
[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 }, 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(seq);
145                 break;
146               }
147             }
148           }
149         }
150
151         return;
152       }
153     }
154     // /////////////////////////////////
155
156     jmb = new AppJmolBinding(this, new PDBEntry[]
157     { pdbentry }, seq, null, null);
158     this.ap = ap;
159     setSize(400, 400); // probably should be a configurable/dynamic default here
160
161     if (pdbentry.getFile() != null)
162     {
163       initJmol("load \"" + pdbentry.getFile() + "\"");
164     }
165     else
166     {
167       Thread worker = new Thread(this);
168       worker.start();
169     }
170
171     this.addInternalFrameListener(new InternalFrameAdapter()
172     {
173       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
174       {
175         closeViewer();
176       }
177     });
178
179   }
180
181   void initJmol(String command)
182   {
183     jmb.setFinishedInit(false);
184     renderPanel = new RenderPanel();
185     // TODO: consider waiting until the structure/view is fully loaded before
186     // displaying
187     this.getContentPane().add(renderPanel, java.awt.BorderLayout.CENTER);
188     jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(),
189             getBounds().width, getBounds().height);
190     jmb.allocateViewer(renderPanel, "", null, null, "");
191     jmb.newJmolPopup(true, "Jmol", true);
192     jmb.evalStateCommand(command);
193     jmb.setFinishedInit(true);
194   }
195
196   void setChainMenuItems(Vector chains)
197   {
198     chainMenu.removeAll();
199     if (chains==null)
200     {
201       return;
202     }
203     JMenuItem menuItem = new JMenuItem("All");
204     menuItem.addActionListener(new ActionListener()
205     {
206       public void actionPerformed(ActionEvent evt)
207       {
208         allChainsSelected = true;
209         for (int i = 0; i < chainMenu.getItemCount(); i++)
210         {
211           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
212             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
213         }
214         centerViewer();
215         allChainsSelected = false;
216       }
217     });
218
219     chainMenu.add(menuItem);
220
221     for (int c = 0; c < chains.size(); c++)
222     {
223       menuItem = new JCheckBoxMenuItem(chains.elementAt(c).toString(), true);
224       menuItem.addItemListener(new ItemListener()
225       {
226         public void itemStateChanged(ItemEvent evt)
227         {
228           if (!allChainsSelected)
229             centerViewer();
230         }
231       });
232
233       chainMenu.add(menuItem);
234     }
235   }
236
237   boolean allChainsSelected = false;
238
239   void centerViewer()
240   {
241     Vector toshow = new Vector();
242     String lbl;
243     int mlength, p, mnum;
244     for (int i = 0; i < chainMenu.getItemCount(); i++)
245     {
246       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
247       {
248         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
249         if (item.isSelected())
250         {
251           toshow.addElement(item.getText());
252         }
253       }
254     }
255     jmb.centerViewer(toshow);
256   }
257
258   void closeViewer()
259   {
260     jmb.closeViewer();
261     // TODO: check for memory leaks where instance isn't finalised because jmb
262     // holds a reference to the window
263     jmb = null;
264   }
265
266   public void run()
267   {
268     String pdbid = "";
269     // todo - record which pdbids were successfuly imported.
270     StringBuffer errormsgs = new StringBuffer(), files = new StringBuffer();
271     try
272     {
273       // TODO: replace with reference fetching/transfer code (validate PDBentry
274       // as a DBRef?)
275       jalview.ws.dbsources.Pdb pdbclient = new jalview.ws.dbsources.Pdb();
276       for (int pi = 0; pi < jmb.pdbentry.length; pi++)
277       {
278         AlignmentI pdbseq;
279         if ((pdbseq = pdbclient.getSequenceRecords(pdbid = jmb.pdbentry[pi]
280                 .getId())) != null)
281         {
282           String file;
283           // just transfer the file name from the first sequence's first
284           // PDBEntry
285           jmb.pdbentry[pi].setFile(file = ((PDBEntry) pdbseq.getSequenceAt(
286                   0).getPDBId().elementAt(0)).getFile());
287           files.append("\"" + file + "\"");
288         }
289         else
290         {
291           errormsgs.append("'" + pdbid + "' ");
292         }
293       }
294     } catch (OutOfMemoryError oomerror)
295     {
296       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
297     } catch (Exception ex)
298     {
299       ex.printStackTrace();
300       errormsgs.append("'" + pdbid + "'");
301     }
302     if (errormsgs.length() > 0)
303     {
304
305       JOptionPane.showInternalMessageDialog(Desktop.desktop,
306               "The following pdb entries could not be retrieved from the PDB:\n"
307                       + errormsgs.toString()
308                       + "\nPlease try downloading them manually.",
309               "Couldn't load file", JOptionPane.ERROR_MESSAGE);
310
311     }
312     if (files.length() > 0)
313     {
314       try
315       {
316         initJmol("load FILES " + files.toString());
317       } catch (OutOfMemoryError oomerror)
318       {
319         new OOMWarning("When trying to open the Jmol viewer!", oomerror);
320         Cache.log.debug("File locations are " + files);
321       } catch (Exception ex)
322       {
323         Cache.log.error("Couldn't open Jmol viewer!", ex);
324       }
325     }
326   }
327
328   public void pdbFile_actionPerformed(ActionEvent actionEvent)
329   {
330     JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache
331             .getProperty("LAST_DIRECTORY"));
332
333     chooser.setFileView(new JalviewFileView());
334     chooser.setDialogTitle("Save PDB File");
335     chooser.setToolTipText("Save");
336
337     int value = chooser.showSaveDialog(this);
338
339     if (value == JalviewFileChooser.APPROVE_OPTION)
340     {
341       try
342       {
343         // TODO: cope with multiple PDB files in view 
344         BufferedReader in = new BufferedReader(new FileReader(jmb.getPdbFile()[0]));
345         File outFile = chooser.getSelectedFile();
346
347         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
348         String data;
349         while ((data = in.readLine()) != null)
350         {
351           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
352           {
353             out.println(data);
354           }
355         }
356         out.close();
357       } catch (Exception ex)
358       {
359         ex.printStackTrace();
360       }
361     }
362   }
363
364   public void viewMapping_actionPerformed(ActionEvent actionEvent)
365   {
366     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
367     try {for (int pdbe = 0; pdbe<jmb.pdbentry.length; pdbe++) {
368       cap.appendText(StructureSelectionManager.getStructureSelectionManager()
369             .printMapping(jmb.pdbentry[pdbe].getFile()));
370       cap.appendText("\n");
371     }} catch (OutOfMemoryError e)
372     {
373       new OOMWarning("composing sequence-structure alignments for display in text box.", e);
374       cap.dispose();
375       return;
376     }
377     jalview.gui.Desktop.addInternalFrame(cap, "PDB - Sequence Mapping",
378             550, 600);
379   }
380
381   /**
382    * DOCUMENT ME!
383    * 
384    * @param e
385    *          DOCUMENT ME!
386    */
387   public void eps_actionPerformed(ActionEvent e)
388   {
389     makePDBImage(jalview.util.ImageMaker.EPS);
390   }
391
392   /**
393    * DOCUMENT ME!
394    * 
395    * @param e
396    *          DOCUMENT ME!
397    */
398   public void png_actionPerformed(ActionEvent e)
399   {
400     makePDBImage(jalview.util.ImageMaker.PNG);
401   }
402
403   void makePDBImage(int type)
404   {
405     int width = getWidth();
406     int height = getHeight();
407
408     jalview.util.ImageMaker im;
409
410     if (type == jalview.util.ImageMaker.PNG)
411     {
412       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.PNG,
413               "Make PNG image from view", width, height, null, null);
414     }
415     else
416     {
417       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.EPS,
418               "Make EPS file from view", width, height, null, this
419                       .getTitle());
420     }
421
422     if (im.getGraphics() != null)
423     {
424       Rectangle rect = new Rectangle(width, height);
425       jmb.viewer.renderScreenImage(im.getGraphics(), rect.getSize(), rect);
426       im.writeImage();
427     }
428   }
429
430   public void seqColour_actionPerformed(ActionEvent actionEvent)
431   {
432     jmb.setColourBySequence(seqColour.isSelected());
433     // Set the colour using the current view for the associated alignframe
434     jmb.colourBySequence(ap.alignFrame.viewport.showSequenceFeatures, ap.alignFrame.viewport.alignment);
435   }
436
437   public void chainColour_actionPerformed(ActionEvent actionEvent)
438   {
439     chainColour.setSelected(true);
440     jmb.colourByChain();
441   }
442
443   public void chargeColour_actionPerformed(ActionEvent actionEvent)
444   {
445     chargeColour.setSelected(true);
446     jmb.colourByCharge();
447   }
448
449   public void zappoColour_actionPerformed(ActionEvent actionEvent)
450   {
451     zappoColour.setSelected(true);
452     jmb.setJalviewColourScheme(new ZappoColourScheme());
453   }
454
455   public void taylorColour_actionPerformed(ActionEvent actionEvent)
456   {
457     taylorColour.setSelected(true);
458     jmb.setJalviewColourScheme(new TaylorColourScheme());
459   }
460
461   public void hydroColour_actionPerformed(ActionEvent actionEvent)
462   {
463     hydroColour.setSelected(true);
464     jmb.setJalviewColourScheme(new HydrophobicColourScheme());
465   }
466
467   public void helixColour_actionPerformed(ActionEvent actionEvent)
468   {
469     helixColour.setSelected(true);
470     jmb.setJalviewColourScheme(new HelixColourScheme());
471   }
472
473   public void strandColour_actionPerformed(ActionEvent actionEvent)
474   {
475     strandColour.setSelected(true);
476     jmb.setJalviewColourScheme(new StrandColourScheme());
477   }
478
479   public void turnColour_actionPerformed(ActionEvent actionEvent)
480   {
481     turnColour.setSelected(true);
482     jmb.setJalviewColourScheme(new TurnColourScheme());
483   }
484
485   public void buriedColour_actionPerformed(ActionEvent actionEvent)
486   {
487     buriedColour.setSelected(true);
488     jmb.setJalviewColourScheme(new BuriedColourScheme());
489   }
490
491
492   public void userColour_actionPerformed(ActionEvent actionEvent)
493   {
494     userColour.setSelected(true);
495     new UserDefinedColours(this, null);
496   }
497
498   public void backGround_actionPerformed(ActionEvent actionEvent)
499   {
500     java.awt.Color col = JColorChooser.showDialog(this,
501             "Select Background Colour", null);
502     if (col != null)
503     {
504       jmb.setBackgroundColour(col);
505     }
506   }
507
508   public void jmolHelp_actionPerformed(ActionEvent actionEvent)
509   {
510     try
511     {
512       jalview.util.BrowserLauncher
513               .openURL("http://jmol.sourceforge.net/docs/JmolUserGuide/");
514     } catch (Exception ex)
515     {
516     }
517   }
518
519   public void showConsole(boolean showConsole)
520   {
521     if (scriptWindow == null)
522       scriptWindow = new ScriptWindow(this);
523
524     if (showConsole)
525     {
526       if (splitPane == null)
527       {
528         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
529         splitPane.setTopComponent(renderPanel);
530         splitPane.setBottomComponent(scriptWindow);
531         this.getContentPane().add(splitPane, BorderLayout.CENTER);
532       }
533
534       splitPane.setDividerLocation(getHeight() - 200);
535       splitPane.validate();
536     }
537     else
538     {
539       if (splitPane != null)
540         splitPane.setVisible(false);
541
542       splitPane = null;
543
544       this.getContentPane().add(renderPanel, BorderLayout.CENTER);
545     }
546
547     validate();
548   }
549
550   class RenderPanel extends JPanel
551   {
552     final Dimension currentSize = new Dimension();
553
554     final Rectangle rectClip = new Rectangle();
555
556     public void paintComponent(Graphics g)
557     {
558       getSize(currentSize);
559       g.getClipBounds(rectClip);
560
561       if (jmb.fileLoadingError != null)
562       {
563         g.setColor(Color.black);
564         g.fillRect(0, 0, currentSize.width, currentSize.height);
565         g.setColor(Color.white);
566         g.setFont(new Font("Verdana", Font.BOLD, 14));
567         g.drawString("Error loading file...", 20,
568                 currentSize.height / 2);
569         StringBuffer sb = new StringBuffer();
570         int lines=0;
571         for (int e=0;e<jmb.pdbentry.length; e++)
572         {
573           sb.append(jmb.pdbentry[e].getId());
574           if (e<jmb.pdbentry.length-1) {
575             sb.append(",");
576           }
577
578           if (e==jmb.pdbentry.length-1 || sb.length()>20)
579           {
580             lines++;
581             g.drawString(sb.toString(), 20, currentSize.height/2 - lines*g.getFontMetrics().getHeight());
582           }
583         }
584       }
585       else
586         if (jmb == null || jmb.viewer == null || !jmb.isFinishedInit())
587         {
588           g.setColor(Color.black);
589           g.fillRect(0, 0, currentSize.width, currentSize.height);
590           g.setColor(Color.white);
591           g.setFont(new Font("Verdana", Font.BOLD, 14));
592           g.drawString("Retrieving PDB data....", 20, currentSize.height / 2);
593         }
594         else 
595       {
596         jmb.viewer.renderScreenImage(g, currentSize, rectClip);
597       }
598     }
599   }
600
601   String viewId = null;
602
603   public String getViewId()
604   {
605     if (viewId == null)
606     {
607       viewId = System.currentTimeMillis() + "." + this.hashCode();
608     }
609     return viewId;
610   }
611   public void updateTitleAndMenus()
612   {
613     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
614     {
615       repaint();
616       return;
617     }
618     setChainMenuItems(jmb.chainNames);
619     jmb.colourBySequence(ap.av.getShowSequenceFeatures(), ap.av.alignment);
620
621     this.setTitle(jmb.getViewerTitle());
622   }
623
624   public void setJalviewColourScheme(ColourSchemeI ucs)
625   {
626     jmb.setJalviewColourScheme(ucs);
627     
628   }
629
630 }