Merge JAL-2136_phyre2_integration to develop
[jalview.git] / src / jalview / gui / AppJmol.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
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
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.bin.Cache;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.PDBEntry;
26 import jalview.datamodel.SequenceI;
27 import jalview.gui.StructureViewer.ViewerType;
28 import jalview.structures.models.AAStructureBindingModel;
29 import jalview.util.BrowserLauncher;
30 import jalview.util.MessageManager;
31 import jalview.util.Platform;
32 import jalview.ws.dbsources.Pdb;
33
34 import java.awt.BorderLayout;
35 import java.awt.Color;
36 import java.awt.Dimension;
37 import java.awt.Font;
38 import java.awt.Graphics;
39 import java.awt.Rectangle;
40 import java.awt.event.ActionEvent;
41 import java.io.File;
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Vector;
45
46 import javax.swing.JCheckBoxMenuItem;
47 import javax.swing.JInternalFrame;
48 import javax.swing.JPanel;
49 import javax.swing.JSplitPane;
50 import javax.swing.SwingUtilities;
51 import javax.swing.event.InternalFrameAdapter;
52 import javax.swing.event.InternalFrameEvent;
53
54 public class AppJmol extends StructureViewerBase
55 {
56   // ms to wait for Jmol to load files
57   private static final int JMOL_LOAD_TIMEOUT = 20000;
58
59   private static final String SPACE = " ";
60
61   private static final String BACKSLASH = "\"";
62
63   AppJmolBinding jmb;
64
65   JPanel scriptWindow;
66
67   JSplitPane splitPane;
68
69   RenderPanel renderPanel;
70
71   /**
72    * 
73    * @param files
74    * @param ids
75    * @param seqs
76    * @param ap
77    * @param usetoColour
78    *          - add the alignment panel to the list used for colouring these
79    *          structures
80    * @param useToAlign
81    *          - add the alignment panel to the list used for aligning these
82    *          structures
83    * @param leaveColouringToJmol
84    *          - do not update the colours from any other source. Jmol is
85    *          handling them
86    * @param loadStatus
87    * @param bounds
88    * @param viewid
89    */
90   public AppJmol(String[] files, String[] ids, SequenceI[][] seqs,
91           AlignmentPanel ap, boolean usetoColour, boolean useToAlign,
92           boolean leaveColouringToJmol, String loadStatus,
93           Rectangle bounds, String viewid)
94   {
95     PDBEntry[] pdbentrys = new PDBEntry[files.length];
96     for (int i = 0; i < pdbentrys.length; i++)
97     {
98       // PDBEntry pdbentry = new PDBEntry(files[i], ids[i]);
99       PDBEntry pdbentry = new PDBEntry(ids[i], null, PDBEntry.Type.PDB,
100               files[i]);
101       pdbentrys[i] = pdbentry;
102     }
103     // / TODO: check if protocol is needed to be set, and if chains are
104     // autodiscovered.
105     jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
106             pdbentrys, seqs, null);
107
108     jmb.setLoadingFromArchive(true);
109     addAlignmentPanel(ap);
110     if (useToAlign)
111     {
112       useAlignmentPanelForSuperposition(ap);
113     }
114     initMenus();
115     if (leaveColouringToJmol || !usetoColour)
116     {
117       jmb.setColourBySequence(false);
118       seqColour.setSelected(false);
119       viewerColour.setSelected(true);
120     }
121     else if (usetoColour)
122     {
123       useAlignmentPanelForColourbyseq(ap);
124       jmb.setColourBySequence(true);
125       seqColour.setSelected(true);
126       viewerColour.setSelected(false);
127     }
128     this.setBounds(bounds);
129     setViewId(viewid);
130     // jalview.gui.Desktop.addInternalFrame(this, "Loading File",
131     // bounds.width,bounds.height);
132
133     this.addInternalFrameListener(new InternalFrameAdapter()
134     {
135       @Override
136       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
137       {
138         closeViewer(false);
139       }
140     });
141     initJmol(loadStatus); // pdbentry, seq, JBPCHECK!ˇ
142
143   }
144
145   @Override
146   protected void initMenus()
147   {
148     super.initMenus();
149
150     viewerActionMenu.setText(MessageManager.getString("label.jmol"));
151
152     viewerColour
153             .setText(MessageManager.getString("label.colour_with_jmol"));
154     viewerColour.setToolTipText(MessageManager
155             .getString("label.let_jmol_manage_structure_colours"));
156   }
157
158   IProgressIndicator progressBar = null;
159
160   @Override
161   protected IProgressIndicator getIProgressIndicator()
162   {
163     return progressBar;
164   }
165   /**
166    * add a single PDB structure to a new or existing Jmol view
167    * 
168    * @param pdbentry
169    * @param seq
170    * @param chains
171    * @param ap
172    */
173   public AppJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
174           final AlignmentPanel ap)
175   {
176     progressBar = ap.alignFrame;
177     String pdbId = pdbentry.getId();
178
179     /*
180      * If the PDB file is already loaded, the user may just choose to add to an
181      * existing viewer (or cancel)
182      */
183     if (addAlreadyLoadedFile(seq, chains, ap, pdbId))
184     {
185       return;
186     }
187
188     /*
189      * Check if there are other Jmol views involving this alignment and prompt
190      * user about adding this molecule to one of them
191      */
192     if (addToExistingViewer(pdbentry, seq, chains, ap, pdbId))
193     {
194       return;
195     }
196
197     /*
198      * If the options above are declined or do not apply, open a new viewer
199      */
200     openNewJmol(ap, new PDBEntry[] { pdbentry }, new SequenceI[][] { seq });
201   }
202
203   private void openNewJmol(AlignmentPanel ap, PDBEntry[] pdbentrys,
204           SequenceI[][] seqs)
205   {
206     progressBar = ap.alignFrame;
207     jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
208             pdbentrys, seqs, null);
209     addAlignmentPanel(ap);
210     useAlignmentPanelForColourbyseq(ap);
211
212     if (pdbentrys.length > 1)
213     {
214       alignAddedStructures = true;
215       useAlignmentPanelForSuperposition(ap);
216     }
217     jmb.setColourBySequence(true);
218     setSize(400, 400); // probably should be a configurable/dynamic default here
219     initMenus();
220     addingStructures = false;
221     worker = new Thread(this);
222     worker.start();
223
224     this.addInternalFrameListener(new InternalFrameAdapter()
225     {
226       @Override
227       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
228       {
229         closeViewer(false);
230       }
231     });
232
233   }
234
235   /**
236    * create a new Jmol containing several structures superimposed using the
237    * given alignPanel.
238    * 
239    * @param ap
240    * @param pe
241    * @param seqs
242    */
243   public AppJmol(AlignmentPanel ap, PDBEntry[] pe, SequenceI[][] seqs)
244   {
245     openNewJmol(ap, pe, seqs);
246   }
247
248   /**
249    * Returns a list of any Jmol viewers. The list is restricted to those linked
250    * to the given alignment panel if it is not null.
251    */
252   @Override
253   protected List<StructureViewerBase> getViewersFor(AlignmentPanel apanel)
254   {
255     List<StructureViewerBase> result = new ArrayList<StructureViewerBase>();
256     JInternalFrame[] frames = Desktop.instance.getAllFrames();
257
258     for (JInternalFrame frame : frames)
259     {
260       if (frame instanceof AppJmol)
261       {
262         if (apanel == null
263                 || ((StructureViewerBase) frame).isLinkedWith(apanel))
264         {
265           result.add((StructureViewerBase) frame);
266         }
267       }
268     }
269     return result;
270   }
271
272   void initJmol(String command)
273   {
274     jmb.setFinishedInit(false);
275     renderPanel = new RenderPanel();
276     // TODO: consider waiting until the structure/view is fully loaded before
277     // displaying
278     this.getContentPane().add(renderPanel, java.awt.BorderLayout.CENTER);
279     jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(),
280             getBounds().width, getBounds().height);
281     if (scriptWindow == null)
282     {
283       BorderLayout bl = new BorderLayout();
284       bl.setHgap(0);
285       bl.setVgap(0);
286       scriptWindow = new JPanel(bl);
287       scriptWindow.setVisible(false);
288     }
289
290     jmb.allocateViewer(renderPanel, true, "", null, null, "",
291             scriptWindow, null);
292     // jmb.newJmolPopup("Jmol");
293     if (command == null)
294     {
295       command = "";
296     }
297     jmb.evalStateCommand(command);
298     jmb.evalStateCommand("set hoverDelay=0.1");
299     jmb.setFinishedInit(true);
300   }
301
302
303
304   boolean allChainsSelected = false;
305
306   @Override
307   void showSelectedChains()
308   {
309     Vector<String> toshow = new Vector<String>();
310     for (int i = 0; i < chainMenu.getItemCount(); i++)
311     {
312       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
313       {
314         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
315         if (item.isSelected())
316         {
317           toshow.addElement(item.getText());
318         }
319       }
320     }
321     jmb.centerViewer(toshow);
322   }
323
324   @Override
325   public void closeViewer(boolean closeExternalViewer)
326   {
327     // Jmol does not use an external viewer
328     if (jmb != null)
329     {
330       jmb.closeViewer();
331     }
332     setAlignmentPanel(null);
333     _aps.clear();
334     _alignwith.clear();
335     _colourwith.clear();
336     // TODO: check for memory leaks where instance isn't finalised because jmb
337     // holds a reference to the window
338     jmb = null;
339   }
340
341   @Override
342   public void run()
343   {
344     _started = true;
345     try
346     {
347       List<String> files = fetchPdbFiles();
348       if (files.size() > 0)
349       {
350         showFilesInViewer(files);
351       }
352     } finally
353     {
354       _started = false;
355       worker = null;
356     }
357   }
358
359   /**
360    * Either adds the given files to a structure viewer or opens a new viewer to
361    * show them
362    * 
363    * @param files
364    *          list of absolute paths to structure files
365    */
366   void showFilesInViewer(List<String> files)
367   {
368     long lastnotify = jmb.getLoadNotifiesHandled();
369     StringBuilder fileList = new StringBuilder();
370     for (String s : files)
371     {
372       fileList.append(SPACE).append(BACKSLASH)
373               .append(Platform.escapeString(s)).append(BACKSLASH);
374     }
375     String filesString = fileList.toString();
376
377     if (!addingStructures)
378     {
379       try
380       {
381         initJmol("load FILES " + filesString);
382       } catch (OutOfMemoryError oomerror)
383       {
384         new OOMWarning("When trying to open the Jmol viewer!", oomerror);
385         Cache.log.debug("File locations are " + filesString);
386       } catch (Exception ex)
387       {
388         Cache.log.error("Couldn't open Jmol viewer!", ex);
389       }
390     }
391     else
392     {
393       StringBuilder cmd = new StringBuilder();
394       cmd.append("loadingJalviewdata=true\nload APPEND ");
395       cmd.append(filesString);
396       cmd.append("\nloadingJalviewdata=null");
397       final String command = cmd.toString();
398       lastnotify = jmb.getLoadNotifiesHandled();
399
400       try
401       {
402         jmb.evalStateCommand(command);
403       } catch (OutOfMemoryError oomerror)
404       {
405         new OOMWarning("When trying to add structures to the Jmol viewer!",
406                 oomerror);
407         Cache.log.debug("File locations are " + filesString);
408       } catch (Exception ex)
409       {
410         Cache.log.error("Couldn't add files to Jmol viewer!", ex);
411       }
412     }
413
414     // need to wait around until script has finished
415     int waitMax = JMOL_LOAD_TIMEOUT;
416     int waitFor = 35;
417     int waitTotal = 0;
418     while (addingStructures ? lastnotify >= jmb.getLoadNotifiesHandled()
419             : !(jmb.isFinishedInit() && jmb.getPdbFile() != null && jmb
420                     .getPdbFile().length == files.size()))
421     {
422       try
423       {
424         Cache.log.debug("Waiting around for jmb notify.");
425         Thread.sleep(waitFor);
426         waitTotal += waitFor;
427       } catch (Exception e)
428       {
429       }
430       if (waitTotal > waitMax)
431       {
432         System.err
433                 .println("Timed out waiting for Jmol to load files after "
434                         + waitTotal + "ms");
435 //        System.err.println("finished: " + jmb.isFinishedInit()
436 //                + "; loaded: " + Arrays.toString(jmb.getPdbFile())
437 //                + "; files: " + files.toString());
438         jmb.getPdbFile();
439         break;
440       }
441     }
442
443     // refresh the sequence colours for the new structure(s)
444     for (AlignmentPanel ap : _colourwith)
445     {
446       jmb.updateColours(ap);
447     }
448     // do superposition if asked to
449     if (Cache.getDefault("AUTOSUPERIMPOSE", true) && alignAddedStructures)
450     {
451       alignAddedStructures();
452     }
453     addingStructures = false;
454   }
455
456   /**
457    * Queues a thread to align structures with Jalview alignments
458    */
459   void alignAddedStructures()
460   {
461     javax.swing.SwingUtilities.invokeLater(new Runnable()
462     {
463       @Override
464       public void run()
465       {
466         if (jmb.viewer.isScriptExecuting())
467         {
468           SwingUtilities.invokeLater(this);
469           try
470           {
471             Thread.sleep(5);
472           } catch (InterruptedException q)
473           {
474           }
475           return;
476         }
477         else
478         {
479           alignStructs_withAllAlignPanels();
480         }
481       }
482     });
483     alignAddedStructures = false;
484   }
485
486   /**
487    * Retrieves and saves as file any modelled PDB entries for which we do not
488    * already have a file saved. Returns a list of absolute paths to structure
489    * files which were either retrieved, or already stored but not modelled in
490    * the structure viewer (i.e. files to add to the viewer display).
491    * 
492    * @return
493    */
494   List<String> fetchPdbFiles()
495   {
496     // todo - record which pdbids were successfully imported.
497     StringBuilder errormsgs = new StringBuilder();
498
499     List<String> files = new ArrayList<String>();
500     String pdbid = "";
501     try
502     {
503       String[] filesInViewer = jmb.getPdbFile();
504       // TODO: replace with reference fetching/transfer code (validate PDBentry
505       // as a DBRef?)
506       Pdb pdbclient = new Pdb();
507       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
508       {
509         String file = jmb.getPdbEntry(pi).getFile();
510         if (file == null)
511         {
512           // retrieve the pdb and store it locally
513           AlignmentI pdbseq = null;
514           pdbid = jmb.getPdbEntry(pi).getId();
515           long hdl = pdbid.hashCode() - System.currentTimeMillis();
516           if (progressBar != null)
517           {
518             progressBar.setProgressBar(MessageManager.formatMessage(
519                     "status.fetching_pdb", new String[] { pdbid }), hdl);
520           }
521           try
522           {
523             pdbseq = pdbclient.getSequenceRecords(pdbid);
524           } catch (OutOfMemoryError oomerror)
525           {
526             new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
527           } catch (Exception ex)
528           {
529             ex.printStackTrace();
530             errormsgs.append("'").append(pdbid).append("'");
531           } finally
532           {
533             if (progressBar != null)
534             {
535               progressBar.setProgressBar(
536                       MessageManager.getString("label.state_completed"),
537                       hdl);
538             }
539           }
540           if (pdbseq != null)
541           {
542             // just transfer the file name from the first sequence's first
543             // PDBEntry
544             file = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
545                     .elementAt(0).getFile()).getAbsolutePath();
546             jmb.getPdbEntry(pi).setFile(file);
547             files.add(file);
548           }
549           else
550           {
551             errormsgs.append("'").append(pdbid).append("' ");
552           }
553         }
554         else
555         {
556           if (filesInViewer != null && filesInViewer.length > 0)
557           {
558             addingStructures = true; // already files loaded.
559             for (int c = 0; c < filesInViewer.length; c++)
560             {
561               if (filesInViewer[c].equals(file))
562               {
563                 file = null;
564                 break;
565               }
566             }
567           }
568           if (file != null)
569           {
570             files.add(file);
571           }
572         }
573       }
574     } catch (OutOfMemoryError oomerror)
575     {
576       new OOMWarning("Retrieving PDB files: " + pdbid, oomerror);
577     } catch (Exception ex)
578     {
579       ex.printStackTrace();
580       errormsgs.append("When retrieving pdbfiles : current was: '")
581               .append(pdbid).append("'");
582     }
583     if (errormsgs.length() > 0)
584     {
585       JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
586               .formatMessage("label.pdb_entries_couldnt_be_retrieved",
587                       new String[] { errormsgs.toString() }),
588               MessageManager.getString("label.couldnt_load_file"),
589               JvOptionPane.ERROR_MESSAGE);
590     }
591     return files;
592   }
593
594   @Override
595   public void eps_actionPerformed(ActionEvent e)
596   {
597     makePDBImage(jalview.util.ImageMaker.TYPE.EPS);
598   }
599
600   @Override
601   public void png_actionPerformed(ActionEvent e)
602   {
603     makePDBImage(jalview.util.ImageMaker.TYPE.PNG);
604   }
605
606   void makePDBImage(jalview.util.ImageMaker.TYPE type)
607   {
608     int width = getWidth();
609     int height = getHeight();
610
611     jalview.util.ImageMaker im;
612
613     if (type == jalview.util.ImageMaker.TYPE.PNG)
614     {
615       im = new jalview.util.ImageMaker(this,
616               jalview.util.ImageMaker.TYPE.PNG, "Make PNG image from view",
617               width, height, null, null, null, 0, false);
618     }
619     else if (type == jalview.util.ImageMaker.TYPE.EPS)
620     {
621       im = new jalview.util.ImageMaker(this,
622               jalview.util.ImageMaker.TYPE.EPS, "Make EPS file from view",
623               width, height, null, this.getTitle(), null, 0, false);
624     }
625     else
626     {
627
628       im = new jalview.util.ImageMaker(this,
629               jalview.util.ImageMaker.TYPE.SVG, "Make SVG file from PCA",
630               width, height, null, this.getTitle(), null, 0, false);
631     }
632
633     if (im.getGraphics() != null)
634     {
635       jmb.viewer.renderScreenImage(im.getGraphics(), width, height);
636       im.writeImage();
637     }
638   }
639
640   @Override
641   public void showHelp_actionPerformed(ActionEvent actionEvent)
642   {
643     try
644     {
645       BrowserLauncher
646               .openURL("http://jmol.sourceforge.net/docs/JmolUserGuide/");
647     } catch (Exception ex)
648     {
649     }
650   }
651
652   public void showConsole(boolean showConsole)
653   {
654
655     if (showConsole)
656     {
657       if (splitPane == null)
658       {
659         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
660         splitPane.setTopComponent(renderPanel);
661         splitPane.setBottomComponent(scriptWindow);
662         this.getContentPane().add(splitPane, BorderLayout.CENTER);
663         splitPane.setDividerLocation(getHeight() - 200);
664         scriptWindow.setVisible(true);
665         scriptWindow.validate();
666         splitPane.validate();
667       }
668
669     }
670     else
671     {
672       if (splitPane != null)
673       {
674         splitPane.setVisible(false);
675       }
676
677       splitPane = null;
678
679       this.getContentPane().add(renderPanel, BorderLayout.CENTER);
680     }
681
682     validate();
683   }
684
685   class RenderPanel extends JPanel
686   {
687     final Dimension currentSize = new Dimension();
688
689     @Override
690     public void paintComponent(Graphics g)
691     {
692       getSize(currentSize);
693
694       if (jmb != null && jmb.hasFileLoadingError())
695       {
696         g.setColor(Color.black);
697         g.fillRect(0, 0, currentSize.width, currentSize.height);
698         g.setColor(Color.white);
699         g.setFont(new Font("Verdana", Font.BOLD, 14));
700         g.drawString(MessageManager.getString("label.error_loading_file")
701                 + "...", 20, currentSize.height / 2);
702         StringBuffer sb = new StringBuffer();
703         int lines = 0;
704         for (int e = 0; e < jmb.getPdbCount(); e++)
705         {
706           sb.append(jmb.getPdbEntry(e).getId());
707           if (e < jmb.getPdbCount() - 1)
708           {
709             sb.append(",");
710           }
711
712           if (e == jmb.getPdbCount() - 1 || sb.length() > 20)
713           {
714             lines++;
715             g.drawString(sb.toString(), 20, currentSize.height / 2 - lines
716                     * g.getFontMetrics().getHeight());
717           }
718         }
719       }
720       else if (jmb == null || jmb.viewer == null || !jmb.isFinishedInit())
721       {
722         g.setColor(Color.black);
723         g.fillRect(0, 0, currentSize.width, currentSize.height);
724         g.setColor(Color.white);
725         g.setFont(new Font("Verdana", Font.BOLD, 14));
726         g.drawString(MessageManager.getString("label.retrieving_pdb_data"),
727                 20, currentSize.height / 2);
728       }
729       else
730       {
731         jmb.viewer.renderScreenImage(g, currentSize.width,
732                 currentSize.height);
733       }
734     }
735   }
736
737   @Override
738   public AAStructureBindingModel getBinding()
739   {
740     return this.jmb;
741   }
742
743   @Override
744   public String getStateInfo()
745   {
746     return jmb == null ? null : jmb.viewer.getStateInfo();
747   }
748
749   @Override
750   public ViewerType getViewerType()
751   {
752     return ViewerType.JMOL;
753   }
754
755   @Override
756   protected String getViewerName()
757   {
758     return "Jmol";
759   }
760
761   @Override
762   protected AAStructureBindingModel getBindingModel()
763   {
764     return jmb;
765   }
766 }