JAL-3855 check if an alpha fold ID and retrieve using that source
[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 java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.Font;
27 import java.awt.Graphics;
28 import java.io.File;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Map;
32
33 import javax.swing.JPanel;
34 import javax.swing.JSplitPane;
35 import javax.swing.SwingUtilities;
36 import javax.swing.event.InternalFrameAdapter;
37 import javax.swing.event.InternalFrameEvent;
38
39 import jalview.api.AlignmentViewPanel;
40 import jalview.bin.Cache;
41 import jalview.datamodel.AlignmentI;
42 import jalview.datamodel.PDBEntry;
43 import jalview.datamodel.SequenceI;
44 import jalview.datamodel.StructureViewerModel;
45 import jalview.datamodel.StructureViewerModel.StructureData;
46 import jalview.gui.ImageExporter.ImageWriterI;
47 import jalview.gui.StructureViewer.ViewerType;
48 import jalview.structure.StructureCommand;
49 import jalview.structures.models.AAStructureBindingModel;
50 import jalview.util.BrowserLauncher;
51 import jalview.util.ImageMaker;
52 import jalview.util.MessageManager;
53 import jalview.util.Platform;
54 import jalview.ws.dbsources.EBIAlfaFold;
55 import jalview.ws.dbsources.Pdb;
56
57 public class AppJmol extends StructureViewerBase
58 {
59   // ms to wait for Jmol to load files
60   private static final int JMOL_LOAD_TIMEOUT = 20000;
61
62   private static final String SPACE = " ";
63
64   private static final String QUOTE = "\"";
65
66   AppJmolBinding jmb;
67
68   JPanel scriptWindow;
69
70   JSplitPane splitPane;
71
72   RenderPanel renderPanel;
73
74   /**
75    * 
76    * @param files
77    * @param ids
78    * @param seqs
79    * @param ap
80    * @param usetoColour
81    *          - add the alignment panel to the list used for colouring these
82    *          structures
83    * @param useToAlign
84    *          - add the alignment panel to the list used for aligning these
85    *          structures
86    * @param leaveColouringToJmol
87    *          - do not update the colours from any other source. Jmol is
88    *          handling them
89    * @param loadStatus
90    * @param bounds
91    * @param viewid
92    */
93   public AppJmol(StructureViewerModel viewerModel, AlignmentPanel ap,
94           String sessionFile, String viewid)
95   {
96     Map<File, StructureData> pdbData = viewerModel.getFileData();
97     PDBEntry[] pdbentrys = new PDBEntry[pdbData.size()];
98     SequenceI[][] seqs = new SequenceI[pdbData.size()][];
99     int i = 0;
100     for (StructureData data : pdbData.values())
101     {
102       PDBEntry pdbentry = new PDBEntry(data.getPdbId(), null,
103               PDBEntry.Type.PDB, data.getFilePath());
104       pdbentrys[i] = pdbentry;
105       List<SequenceI> sequencesForPdb = data.getSeqList();
106       seqs[i] = sequencesForPdb
107               .toArray(new SequenceI[sequencesForPdb.size()]);
108       i++;
109     }
110
111     // TODO: check if protocol is needed to be set, and if chains are
112     // autodiscovered.
113     jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
114             pdbentrys, seqs, null);
115
116     jmb.setLoadingFromArchive(true);
117     addAlignmentPanel(ap);
118     if (viewerModel.isAlignWithPanel())
119     {
120       useAlignmentPanelForSuperposition(ap);
121     }
122     initMenus();
123     boolean useToColour = viewerModel.isColourWithAlignPanel();
124     boolean leaveColouringToJmol = viewerModel.isColourByViewer();
125     if (leaveColouringToJmol || !useToColour)
126     {
127       jmb.setColourBySequence(false);
128       seqColour.setSelected(false);
129       viewerColour.setSelected(true);
130     }
131     else if (useToColour)
132     {
133       useAlignmentPanelForColourbyseq(ap);
134       jmb.setColourBySequence(true);
135       seqColour.setSelected(true);
136       viewerColour.setSelected(false);
137     }
138
139     this.setBounds(viewerModel.getX(), viewerModel.getY(),
140             viewerModel.getWidth(), viewerModel.getHeight());
141     setViewId(viewid);
142
143     this.addInternalFrameListener(new InternalFrameAdapter()
144     {
145       @Override
146       public void internalFrameClosing(
147               InternalFrameEvent internalFrameEvent)
148       {
149         closeViewer(false);
150       }
151     });
152     StringBuilder cmd = new StringBuilder();
153     cmd.append("load FILES ").append(QUOTE)
154             .append(Platform.escapeBackslashes(sessionFile)).append(QUOTE);
155     initJmol(cmd.toString());
156   }
157
158   @Override
159   protected void initMenus()
160   {
161     super.initMenus();
162
163     viewerColour
164             .setText(MessageManager.getString("label.colour_with_jmol"));
165     viewerColour.setToolTipText(MessageManager
166             .getString("label.let_jmol_manage_structure_colours"));
167   }
168
169   /**
170    * display a single PDB structure in a new Jmol view
171    * 
172    * @param pdbentry
173    * @param seq
174    * @param chains
175    * @param ap
176    */
177   public AppJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
178           final AlignmentPanel ap)
179   {
180     setProgressIndicator(ap.alignFrame);
181
182     openNewJmol(ap, alignAddedStructures, new PDBEntry[] { pdbentry },
183             new SequenceI[][]
184             { seq });
185   }
186
187   private void openNewJmol(AlignmentPanel ap, boolean alignAdded,
188           PDBEntry[] pdbentrys,
189           SequenceI[][] seqs)
190   {
191     setProgressIndicator(ap.alignFrame);
192     jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
193             pdbentrys, seqs, null);
194     addAlignmentPanel(ap);
195     useAlignmentPanelForColourbyseq(ap);
196
197     alignAddedStructures = alignAdded;
198     if (pdbentrys.length > 1)
199     {
200       useAlignmentPanelForSuperposition(ap);
201     }
202
203     jmb.setColourBySequence(true);
204     setSize(400, 400); // probably should be a configurable/dynamic default here
205     initMenus();
206     addingStructures = false;
207     worker = new Thread(this);
208     worker.start();
209
210     this.addInternalFrameListener(new InternalFrameAdapter()
211     {
212       @Override
213       public void internalFrameClosing(
214               InternalFrameEvent internalFrameEvent)
215       {
216         closeViewer(false);
217       }
218     });
219
220   }
221
222   /**
223    * create a new Jmol containing several structures optionally superimposed
224    * using the given alignPanel.
225    * 
226    * @param ap
227    * @param alignAdded
228    *          - true to superimpose
229    * @param pe
230    * @param seqs
231    */
232   public AppJmol(AlignmentPanel ap, boolean alignAdded, PDBEntry[] pe,
233           SequenceI[][] seqs)
234   {
235     openNewJmol(ap, alignAdded, pe, seqs);
236   }
237
238
239   void initJmol(String command)
240   {
241     jmb.setFinishedInit(false);
242     renderPanel = new RenderPanel();
243     // TODO: consider waiting until the structure/view is fully loaded before
244     // displaying
245     this.getContentPane().add(renderPanel, java.awt.BorderLayout.CENTER);
246     jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(),
247             getBounds().width, getBounds().height);
248     if (scriptWindow == null)
249     {
250       BorderLayout bl = new BorderLayout();
251       bl.setHgap(0);
252       bl.setVgap(0);
253       scriptWindow = new JPanel(bl);
254       scriptWindow.setVisible(false);
255     }
256
257     jmb.allocateViewer(renderPanel, true, "", null, null, "", scriptWindow,
258             null);
259     // jmb.newJmolPopup("Jmol");
260     if (command == null)
261     {
262       command = "";
263     }
264     jmb.executeCommand(new StructureCommand(command), false);
265     jmb.executeCommand(new StructureCommand("set hoverDelay=0.1"), false);
266     jmb.setFinishedInit(true);
267   }
268
269   @Override
270   public void run()
271   {
272     _started = true;
273     try
274     {
275       List<String> files = fetchPdbFiles();
276       if (files.size() > 0)
277       {
278         showFilesInViewer(files);
279       }
280     } finally
281     {
282       _started = false;
283       worker = null;
284     }
285   }
286
287   /**
288    * Either adds the given files to a structure viewer or opens a new viewer to
289    * show them
290    * 
291    * @param files
292    *          list of absolute paths to structure files
293    */
294   void showFilesInViewer(List<String> files)
295   {
296     long lastnotify = jmb.getLoadNotifiesHandled();
297     StringBuilder fileList = new StringBuilder();
298     for (String s : files)
299     {
300       fileList.append(SPACE).append(QUOTE)
301               .append(Platform.escapeBackslashes(s)).append(QUOTE);
302     }
303     String filesString = fileList.toString();
304
305     if (!addingStructures)
306     {
307       try
308       {
309         initJmol("load FILES " + filesString);
310       } catch (OutOfMemoryError oomerror)
311       {
312         new OOMWarning("When trying to open the Jmol viewer!", oomerror);
313         Cache.log.debug("File locations are " + filesString);
314       } catch (Exception ex)
315       {
316         Cache.log.error("Couldn't open Jmol viewer!", ex);
317         ex.printStackTrace();
318         return;
319       }
320     }
321     else
322     {
323       StringBuilder cmd = new StringBuilder();
324       cmd.append("loadingJalviewdata=true\nload APPEND ");
325       cmd.append(filesString);
326       cmd.append("\nloadingJalviewdata=null");
327       final StructureCommand command = new StructureCommand(cmd.toString());
328       lastnotify = jmb.getLoadNotifiesHandled();
329
330       try
331       {
332         jmb.executeCommand(command, false);
333       } catch (OutOfMemoryError oomerror)
334       {
335         new OOMWarning("When trying to add structures to the Jmol viewer!",
336                 oomerror);
337         Cache.log.debug("File locations are " + filesString);
338         return;
339       } catch (Exception ex)
340       {
341         Cache.log.error("Couldn't add files to Jmol viewer!", ex);
342         ex.printStackTrace();
343         return;
344       }
345     }
346
347     // need to wait around until script has finished
348     int waitMax = JMOL_LOAD_TIMEOUT;
349     int waitFor = 35;
350     int waitTotal = 0;
351     while (addingStructures ? lastnotify >= jmb.getLoadNotifiesHandled()
352             : !(jmb.isFinishedInit() && jmb.getStructureFiles() != null
353                     && jmb.getStructureFiles().length == files.size()))
354     {
355       try
356       {
357         Cache.log.debug("Waiting around for jmb notify.");
358         waitTotal += waitFor;
359
360         // Thread.sleep() throws an exception in JS
361         Thread.sleep(waitFor);
362       } catch (Exception e)
363       {
364       }
365       if (waitTotal > waitMax)
366       {
367         System.err.println("Timed out waiting for Jmol to load files after "
368                 + waitTotal + "ms");
369         // System.err.println("finished: " + jmb.isFinishedInit()
370         // + "; loaded: " + Arrays.toString(jmb.getPdbFile())
371         // + "; files: " + files.toString());
372         jmb.getStructureFiles();
373         break;
374       }
375     }
376
377     // refresh the sequence colours for the new structure(s)
378     for (AlignmentViewPanel ap : _colourwith)
379     {
380       jmb.updateColours(ap);
381     }
382     // do superposition if asked to
383     if (alignAddedStructures)
384     {
385       alignAddedStructures();
386     }
387     addingStructures = false;
388   }
389
390   /**
391    * Queues a thread to align structures with Jalview alignments
392    */
393   void alignAddedStructures()
394   {
395     javax.swing.SwingUtilities.invokeLater(new Runnable()
396     {
397       @Override
398       public void run()
399       {
400         if (jmb.jmolViewer.isScriptExecuting())
401         {
402           SwingUtilities.invokeLater(this);
403           try
404           {
405             Thread.sleep(5);
406           } catch (InterruptedException q)
407           {
408           }
409           return;
410         }
411         else
412         {
413           alignStructsWithAllAlignPanels();
414         }
415       }
416     });
417
418   }
419
420   /**
421    * Retrieves and saves as file any modelled PDB entries for which we do not
422    * already have a file saved. Returns a list of absolute paths to structure
423    * files which were either retrieved, or already stored but not modelled in
424    * the structure viewer (i.e. files to add to the viewer display).
425    * 
426    * @return
427    */
428   List<String> fetchPdbFiles()
429   {
430     // todo - record which pdbids were successfully imported.
431     StringBuilder errormsgs = new StringBuilder();
432
433     List<String> files = new ArrayList<>();
434     String pdbid = "";
435     try
436     {
437       String[] filesInViewer = jmb.getStructureFiles();
438       // TODO: replace with reference fetching/transfer code (validate PDBentry
439       // as a DBRef?)
440       Pdb pdbclient = new Pdb();
441       EBIAlfaFold afclient = new EBIAlfaFold();
442       
443       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
444       {
445         String file = jmb.getPdbEntry(pi).getFile();
446         if (file == null)
447         {
448           // todo: extract block as method and pull up (also ChimeraViewFrame)
449           // retrieve the pdb and store it locally
450           AlignmentI pdbseq = null;
451           pdbid = jmb.getPdbEntry(pi).getId();
452           long hdl = pdbid.hashCode() - System.currentTimeMillis();
453           setProgressMessage(MessageManager
454                   .formatMessage("status.fetching_pdb", new String[]
455                   { pdbid }), hdl);
456           try
457           {
458             if (afclient.isValidReference(pdbid))
459             {
460               pdbseq = afclient.getSequenceRecords(pdbid);
461             } else {
462               pdbseq = pdbclient.getSequenceRecords(pdbid);
463             }
464           } catch (OutOfMemoryError oomerror)
465           {
466             new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
467           } catch (Exception ex)
468           {
469             ex.printStackTrace();
470             errormsgs.append("'").append(pdbid).append("'");
471           } finally
472           {
473             setProgressMessage(
474                     MessageManager.getString("label.state_completed"), hdl);
475           }
476           if (pdbseq != null)
477           {
478             // just transfer the file name from the first sequence's first
479             // PDBEntry
480             file = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
481                     .elementAt(0).getFile()).getAbsolutePath();
482             jmb.getPdbEntry(pi).setFile(file);
483             files.add(file);
484           }
485           else
486           {
487             errormsgs.append("'").append(pdbid).append("' ");
488           }
489         }
490         else
491         {
492           if (filesInViewer != null && filesInViewer.length > 0)
493           {
494             addingStructures = true; // already files loaded.
495             for (int c = 0; c < filesInViewer.length; c++)
496             {
497               if (Platform.pathEquals(filesInViewer[c], file))
498               {
499                 file = null;
500                 break;
501               }
502             }
503           }
504           if (file != null)
505           {
506             files.add(file);
507           }
508         }
509       }
510     } catch (OutOfMemoryError oomerror)
511     {
512       new OOMWarning("Retrieving PDB files: " + pdbid, oomerror);
513     } catch (Exception ex)
514     {
515       ex.printStackTrace();
516       errormsgs.append("When retrieving pdbfiles : current was: '")
517               .append(pdbid).append("'");
518     }
519     if (errormsgs.length() > 0)
520     {
521       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
522               MessageManager.formatMessage(
523                       "label.pdb_entries_couldnt_be_retrieved", new String[]
524                       { errormsgs.toString() }),
525               MessageManager.getString("label.couldnt_load_file"),
526               JvOptionPane.ERROR_MESSAGE);
527     }
528     return files;
529   }
530
531   /**
532    * Outputs the Jmol viewer image as an image file, after prompting the user to
533    * choose a file and (for EPS) choice of Text or Lineart character rendering
534    * (unless a preference for this is set)
535    * 
536    * @param type
537    */
538   @Override
539   public void makePDBImage(ImageMaker.TYPE type)
540   {
541     int width = getWidth();
542     int height = getHeight();
543     ImageWriterI writer = new ImageWriterI()
544     {
545       @Override
546       public void exportImage(Graphics g) throws Exception
547       {
548         jmb.jmolViewer.renderScreenImage(g, width, height);
549       }
550     };
551     String view = MessageManager.getString("action.view").toLowerCase();
552     ImageExporter exporter = new ImageExporter(writer,
553             getProgressIndicator(), type, getTitle());
554     exporter.doExport(null, this, width, height, view);
555   }
556
557   @Override
558   public void showHelp_actionPerformed()
559   {
560     try
561     {
562       BrowserLauncher // BH 2018
563               .openURL("http://wiki.jmol.org");//http://jmol.sourceforge.net/docs/JmolUserGuide/");
564     } catch (Exception ex)
565     {
566       System.err.println("Show Jmol help failed with: " + ex.getMessage());
567     }
568   }
569
570   @Override
571   public void showConsole(boolean showConsole)
572   {
573     if (showConsole)
574     {
575       if (splitPane == null)
576       {
577         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
578         splitPane.setTopComponent(renderPanel);
579         splitPane.setBottomComponent(scriptWindow);
580         this.getContentPane().add(splitPane, BorderLayout.CENTER);
581         splitPane.setDividerLocation(getHeight() - 200);
582         scriptWindow.setVisible(true);
583         scriptWindow.validate();
584         splitPane.validate();
585       }
586
587     }
588     else
589     {
590       if (splitPane != null)
591       {
592         splitPane.setVisible(false);
593       }
594
595       splitPane = null;
596
597       this.getContentPane().add(renderPanel, BorderLayout.CENTER);
598     }
599
600     validate();
601   }
602
603   class RenderPanel extends JPanel
604   {
605     final Dimension currentSize = new Dimension();
606
607     @Override
608     public void paintComponent(Graphics g)
609     {
610       getSize(currentSize);
611
612       if (jmb != null && jmb.hasFileLoadingError())
613       {
614         g.setColor(Color.black);
615         g.fillRect(0, 0, currentSize.width, currentSize.height);
616         g.setColor(Color.white);
617         g.setFont(new Font("Verdana", Font.BOLD, 14));
618         g.drawString(MessageManager.getString("label.error_loading_file")
619                 + "...", 20, currentSize.height / 2);
620         StringBuffer sb = new StringBuffer();
621         int lines = 0;
622         for (int e = 0; e < jmb.getPdbCount(); e++)
623         {
624           sb.append(jmb.getPdbEntry(e).getId());
625           if (e < jmb.getPdbCount() - 1)
626           {
627             sb.append(",");
628           }
629
630           if (e == jmb.getPdbCount() - 1 || sb.length() > 20)
631           {
632             lines++;
633             g.drawString(sb.toString(), 20, currentSize.height / 2
634                     - lines * g.getFontMetrics().getHeight());
635           }
636         }
637       }
638       else if (jmb == null || jmb.jmolViewer == null || !jmb.isFinishedInit())
639       {
640         g.setColor(Color.black);
641         g.fillRect(0, 0, currentSize.width, currentSize.height);
642         g.setColor(Color.white);
643         g.setFont(new Font("Verdana", Font.BOLD, 14));
644         g.drawString(MessageManager.getString("label.retrieving_pdb_data"),
645                 20, currentSize.height / 2);
646       }
647       else
648       {
649         jmb.jmolViewer.renderScreenImage(g, currentSize.width,
650                 currentSize.height);
651       }
652     }
653   }
654
655   @Override
656   public AAStructureBindingModel getBinding()
657   {
658     return this.jmb;
659   }
660
661   @Override
662   public ViewerType getViewerType()
663   {
664     return ViewerType.JMOL;
665   }
666
667   @Override
668   protected String getViewerName()
669   {
670     return "Jmol";
671   }
672 }