Merge branch 'patch/JAL-4345_pae_epas1_doubleclick' into develop
[jalview.git] / src / jalview / gui / PymolViewer.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.event.ActionEvent;
24 import java.awt.event.ActionListener;
25 import java.io.File;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Map;
29
30 import javax.swing.JInternalFrame;
31 import javax.swing.JMenuItem;
32 import javax.swing.event.InternalFrameAdapter;
33 import javax.swing.event.InternalFrameEvent;
34
35 import jalview.api.AlignmentViewPanel;
36 import jalview.api.FeatureRenderer;
37 import jalview.bin.Console;
38 import jalview.datamodel.PDBEntry;
39 import jalview.datamodel.SequenceI;
40 import jalview.datamodel.StructureViewerModel;
41 import jalview.datamodel.StructureViewerModel.StructureData;
42 import jalview.gui.StructureViewer.ViewerType;
43 import jalview.io.DataSourceType;
44 import jalview.io.StructureFile;
45 import jalview.structures.models.AAStructureBindingModel;
46 import jalview.util.MessageManager;
47
48 public class PymolViewer extends StructureViewerBase
49 {
50   private static final int myWidth = 500;
51
52   private static final int myHeight = 150;
53
54   private PymolBindingModel binding;
55
56   private String pymolSessionFile;
57
58   public PymolViewer()
59   {
60     super();
61
62     /*
63      * closeViewer will decide whether or not to close this frame
64      * depending on whether user chooses to Cancel or not
65      */
66     setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
67   }
68
69   public PymolViewer(PDBEntry pdb, SequenceI[] seqs, Object object,
70           AlignmentPanel ap)
71   {
72     this();
73     openNewPymol(ap, new PDBEntry[] { pdb }, new SequenceI[][] { seqs });
74   }
75
76   public PymolViewer(PDBEntry[] pe, boolean alignAdded, SequenceI[][] seqs,
77           AlignmentPanel ap)
78   {
79     this();
80     setAlignAddedStructures(alignAdded);
81     openNewPymol(ap, pe, seqs);
82   }
83
84   /**
85    * Constructor given a session file to be restored
86    * 
87    * @param sessionFile
88    * @param alignPanel
89    * @param pdbArray
90    * @param seqsArray
91    * @param colourByPymol
92    * @param colourBySequence
93    * @param newViewId
94    */
95   public PymolViewer(StructureViewerModel viewerModel,
96           AlignmentPanel alignPanel, String sessionFile, String vid)
97   {
98     // TODO convert to base/factory class method
99     this();
100     setViewId(vid);
101     this.pymolSessionFile = sessionFile;
102     Map<File, StructureData> pdbData = viewerModel.getFileData();
103     PDBEntry[] pdbArray = new PDBEntry[pdbData.size()];
104     SequenceI[][] seqsArray = new SequenceI[pdbData.size()][];
105     int i = 0;
106     for (StructureData data : pdbData.values())
107     {
108       PDBEntry pdbentry = new PDBEntry(data.getPdbId(), null,
109               PDBEntry.Type.PDB, data.getFilePath());
110       pdbArray[i] = pdbentry;
111       List<SequenceI> sequencesForPdb = data.getSeqList();
112       seqsArray[i] = sequencesForPdb
113               .toArray(new SequenceI[sequencesForPdb.size()]);
114       i++;
115     }
116
117     openNewPymol(alignPanel, pdbArray, seqsArray);
118     if (viewerModel.isColourByViewer())
119     {
120       binding.setColourBySequence(false);
121       seqColour.setSelected(false);
122       viewerColour.setSelected(true);
123     }
124     else if (viewerModel.isColourWithAlignPanel())
125     {
126       binding.setColourBySequence(true);
127       seqColour.setSelected(true);
128       viewerColour.setSelected(false);
129     }
130   }
131
132   private void openNewPymol(AlignmentPanel ap, PDBEntry[] pe,
133           SequenceI[][] seqs)
134   {
135     createProgressBar();
136     binding = new PymolBindingModel(this, ap.getStructureSelectionManager(),
137             pe, seqs);
138     addAlignmentPanel(ap);
139     useAlignmentPanelForColourbyseq(ap);
140
141     if (pe.length > 1)
142     {
143       useAlignmentPanelForSuperposition(ap);
144     }
145     binding.setColourBySequence(true);
146     setSize(myWidth, myHeight);
147     initMenus();
148     viewerActionMenu.setText("PyMOL");
149     updateTitleAndMenus();
150
151     addingStructures = false;
152     worker = new Thread(this);
153     worker.start();
154
155     this.addInternalFrameListener(new InternalFrameAdapter()
156     {
157       @Override
158       public void internalFrameClosing(
159               InternalFrameEvent internalFrameEvent)
160       {
161         closeViewer(false);
162       }
163     });
164
165   }
166
167   /**
168    * Create a helper to manage progress bar display
169    */
170   protected void createProgressBar()
171   {
172     if (getProgressIndicator() == null)
173     {
174       setProgressIndicator(new ProgressBar(statusPanel, statusBar));
175     }
176   }
177
178   @Override
179   public void run()
180   {
181     // todo pull up much of this
182
183     StringBuilder errormsgs = new StringBuilder(128);
184     List<PDBEntry> filePDB = new ArrayList<>();
185     List<Integer> filePDBpos = new ArrayList<>();
186     String[] curfiles = binding.getStructureFiles(); // files currently in
187                                                      // viewer
188     for (int pi = 0; pi < binding.getPdbCount(); pi++)
189     {
190       String file = null;
191       PDBEntry thePdbEntry = binding.getPdbEntry(pi);
192       if (thePdbEntry.getFile() == null)
193       {
194         /*
195          * Retrieve PDB data, save to file, attach to PDBEntry
196          */
197         file = fetchPdbFile(thePdbEntry);
198         if (file == null)
199         {
200           errormsgs.append("'" + thePdbEntry.getId() + "' ");
201         }
202       }
203       else
204       {
205         /*
206          * got file already
207          */
208         file = new File(thePdbEntry.getFile()).getAbsoluteFile().getPath();
209         // todo - skip if already loaded in PyMOL
210       }
211       if (file != null)
212       {
213         filePDB.add(thePdbEntry);
214         filePDBpos.add(Integer.valueOf(pi));
215       }
216     }
217
218     if (!filePDB.isEmpty())
219     {
220       /*
221        * at least one structure to add to viewer
222        */
223       binding.setFinishedInit(false);
224       if (!addingStructures)
225       {
226         try
227         {
228           initPymol();
229         } catch (Exception ex)
230         {
231           Console.error("Couldn't open PyMOL viewer!", ex);
232           // if we couldn't open Pymol, no point continuing
233           return;
234         }
235       }
236       if (!binding.isViewerRunning())
237       {
238         // nothing to do
239         // TODO: ensure we tidy up JAL-3619
240
241         return;
242       }
243
244       int num = -1;
245       for (PDBEntry pe : filePDB)
246       {
247         num++;
248         if (pe.getFile() != null)
249         {
250           try
251           {
252             int pos = filePDBpos.get(num).intValue();
253             long startTime = startProgressBar(getViewerName() + " "
254                     + MessageManager.getString("status.opening_file_for")
255                     + " " + pe.getId());
256             binding.openFile(pe);
257             binding.addSequence(pos, binding.getSequence()[pos]);
258             File fl = new File(pe.getFile());
259             DataSourceType protocol = DataSourceType.URL;
260             try
261             {
262               if (fl.exists())
263               {
264                 protocol = DataSourceType.FILE;
265               }
266             } catch (Throwable e)
267             {
268             } finally
269             {
270               stopProgressBar("", startTime);
271             }
272
273             StructureFile pdb = null;
274             if (pe.hasStructureFile())
275             {
276               pdb = pe.getStructureFile();
277               Console.debug("(Re)Using StructureFile " + pdb.getId());
278             }
279             else
280             {
281               pdb = binding.getSsm().setMapping(binding.getSequence()[pos],
282                       binding.getChains()[pos], pe.getFile(), protocol,
283                       getProgressIndicator());
284             }
285             binding.stashFoundChains(pdb, pe.getFile());
286           } catch (Exception ex)
287           {
288             Console.error("Couldn't open " + pe.getFile() + " in "
289                     + getViewerName() + "!", ex);
290           } finally
291           {
292             // Cache.debug("File locations are " + files);
293           }
294         }
295       }
296
297       binding.refreshGUI();
298       binding.setFinishedInit(true);
299       binding.setLoadingFromArchive(false);
300
301       /*
302        * ensure that any newly discovered features (e.g. RESNUM)
303        * are added to any open feature settings dialog
304        */
305       FeatureRenderer fr = getBinding().getFeatureRenderer(null);
306       if (fr != null)
307       {
308         fr.featuresAdded();
309       }
310
311       // refresh the sequence colours for the new structure(s)
312       for (AlignmentViewPanel ap : _colourwith)
313       {
314         binding.updateColours(ap);
315       }
316       // do superposition if asked to
317       if (alignAddedStructures)
318       {
319         new Thread(new Runnable()
320         {
321           @Override
322           public void run()
323           {
324             alignStructsWithAllAlignPanels();
325           }
326         }).start();
327       }
328       addingStructures = false;
329     }
330     _started = false;
331     worker = null;
332
333   }
334
335   /**
336    * Launch PyMOL. If we have a session file name, send PyMOL the command to
337    * open its saved session file.
338    */
339   void initPymol()
340   {
341     Desktop.addInternalFrame(this,
342             binding.getViewerTitle(getViewerName(), true),
343             getBounds().width, getBounds().height);
344
345     if (!binding.launchPymol())
346     {
347       JvOptionPane.showMessageDialog(Desktop.desktop,
348               MessageManager.formatMessage("label.open_viewer_failed",
349                       getViewerName()),
350               MessageManager.getString("label.error_loading_file"),
351               JvOptionPane.ERROR_MESSAGE);
352       binding.closeViewer(true);
353       this.dispose();
354       return;
355     }
356
357     if (this.pymolSessionFile != null)
358     {
359       boolean opened = binding.openSession(pymolSessionFile);
360       if (!opened)
361       {
362         Console.error("An error occurred opening PyMOL session file "
363                 + pymolSessionFile);
364       }
365     }
366     // binding.startPymolListener();
367   }
368
369   @Override
370   public AAStructureBindingModel getBinding()
371   {
372     return binding;
373   }
374
375   @Override
376   public ViewerType getViewerType()
377   {
378     return ViewerType.PYMOL;
379   }
380
381   @Override
382   protected String getViewerName()
383   {
384     return "PyMOL";
385   }
386
387   JMenuItem writeFeatures = null;
388
389   @Override
390   protected void initMenus()
391   {
392     super.initMenus();
393
394     savemenu.setVisible(false); // not yet implemented
395     viewMenu.add(fitToWindow);
396
397     writeFeatures = new JMenuItem(
398             MessageManager.getString("label.create_viewer_attributes"));
399     writeFeatures.setToolTipText(
400             MessageManager.getString("label.create_viewer_attributes_tip"));
401     writeFeatures.addActionListener(new ActionListener()
402     {
403       @Override
404       public void actionPerformed(ActionEvent e)
405       {
406         sendFeaturesToPymol();
407       }
408     });
409     viewerActionMenu.add(writeFeatures);
410   }
411
412   @Override
413   protected void buildActionMenu()
414   {
415     super.buildActionMenu();
416     viewerActionMenu.add(writeFeatures);
417   }
418
419   protected void sendFeaturesToPymol()
420   {
421     int count = binding.sendFeaturesToViewer(getAlignmentPanel());
422     statusBar.setText(MessageManager.formatMessage("label.attributes_set",
423             count, getViewerName()));
424   }
425
426 }