Merge branch 'JAL-3878_ws-overhaul-3' into with_ws_overhaul-3
[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 = binding.getSsm().setMapping(
274                     binding.getSequence()[pos], binding.getChains()[pos],
275                     pe.getFile(), protocol, getProgressIndicator());
276             binding.stashFoundChains(pdb, pe.getFile());
277           } catch (Exception ex)
278           {
279             Console.error("Couldn't open " + pe.getFile() + " in "
280                     + getViewerName() + "!", ex);
281           } finally
282           {
283             // Cache.debug("File locations are " + files);
284           }
285         }
286       }
287
288       binding.refreshGUI();
289       binding.setFinishedInit(true);
290       binding.setLoadingFromArchive(false);
291
292       /*
293        * ensure that any newly discovered features (e.g. RESNUM)
294        * are added to any open feature settings dialog
295        */
296       FeatureRenderer fr = getBinding().getFeatureRenderer(null);
297       if (fr != null)
298       {
299         fr.featuresAdded();
300       }
301
302       // refresh the sequence colours for the new structure(s)
303       for (AlignmentViewPanel ap : _colourwith)
304       {
305         binding.updateColours(ap);
306       }
307       // do superposition if asked to
308       if (alignAddedStructures)
309       {
310         new Thread(new Runnable()
311         {
312           @Override
313           public void run()
314           {
315             alignStructsWithAllAlignPanels();
316           }
317         }).start();
318       }
319       addingStructures = false;
320     }
321     _started = false;
322     worker = null;
323
324   }
325
326   /**
327    * Launch PyMOL. If we have a session file name, send PyMOL the command to
328    * open its saved session file.
329    */
330   void initPymol()
331   {
332     Desktop.addInternalFrame(this,
333             binding.getViewerTitle(getViewerName(), true),
334             getBounds().width, getBounds().height);
335
336     if (!binding.launchPymol())
337     {
338         JvOptionPane.showMessageDialog(Desktop.getInstance(),
339               MessageManager.formatMessage("label.open_viewer_failed",
340                       getViewerName()),
341               MessageManager.getString("label.error_loading_file"),
342               JvOptionPane.ERROR_MESSAGE);
343       binding.closeViewer(true);
344       this.dispose();
345       return;
346     }
347
348     if (this.pymolSessionFile != null)
349     {
350       boolean opened = binding.openSession(pymolSessionFile);
351       if (!opened)
352       {
353         Console.error("An error occurred opening PyMOL session file "
354                 + pymolSessionFile);
355       }
356     }
357     // binding.startPymolListener();
358   }
359
360   @Override
361   public AAStructureBindingModel getBinding()
362   {
363     return binding;
364   }
365
366   @Override
367   public ViewerType getViewerType()
368   {
369     return ViewerType.PYMOL;
370   }
371
372   @Override
373   protected String getViewerName()
374   {
375     return "PyMOL";
376   }
377
378   JMenuItem writeFeatures = null;
379
380   @Override
381   protected void initMenus()
382   {
383     super.initMenus();
384
385     savemenu.setVisible(false); // not yet implemented
386     viewMenu.add(fitToWindow);
387
388     writeFeatures = new JMenuItem(
389             MessageManager.getString("label.create_viewer_attributes"));
390     writeFeatures.setToolTipText(
391             MessageManager.getString("label.create_viewer_attributes_tip"));
392     writeFeatures.addActionListener(new ActionListener()
393     {
394       @Override
395       public void actionPerformed(ActionEvent e)
396       {
397         sendFeaturesToPymol();
398       }
399     });
400     viewerActionMenu.add(writeFeatures);
401   }
402
403   @Override
404   protected void buildActionMenu()
405   {
406     super.buildActionMenu();
407     viewerActionMenu.add(writeFeatures);
408   }
409
410   protected void sendFeaturesToPymol()
411   {
412     int count = binding.sendFeaturesToViewer(getAlignmentPanel());
413     statusBar.setText(MessageManager.formatMessage("label.attributes_set",
414             count, getViewerName()));
415   }
416
417 }