JAL-3551 working proof of concept of Jalview driving PyMOL
[jalview.git] / src / jalview / gui / PymolViewer.java
1 package jalview.gui;
2
3 import jalview.api.AlignmentViewPanel;
4 import jalview.api.FeatureRenderer;
5 import jalview.bin.Cache;
6 import jalview.datamodel.PDBEntry;
7 import jalview.datamodel.SequenceI;
8 import jalview.gui.StructureViewer.ViewerType;
9 import jalview.io.DataSourceType;
10 import jalview.io.StructureFile;
11 import jalview.structures.models.AAStructureBindingModel;
12 import jalview.util.MessageManager;
13
14 import java.io.File;
15 import java.util.ArrayList;
16 import java.util.List;
17
18 import javax.swing.JInternalFrame;
19 import javax.swing.event.InternalFrameAdapter;
20 import javax.swing.event.InternalFrameEvent;
21
22 public class PymolViewer extends StructureViewerBase
23 {
24   private static final int myWidth = 500;
25
26   private static final int myHeight = 150;
27
28   private PymolBindingModel binding;
29
30   private String pymolSessionFile;
31
32   public PymolViewer()
33   {
34     super();
35
36     /*
37      * closeViewer will decide whether or not to close this frame
38      * depending on whether user chooses to Cancel or not
39      */
40     setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
41   }
42
43   public PymolViewer(PDBEntry pdb, SequenceI[] seqs, Object object,
44           AlignmentPanel ap)
45   {
46     this();
47     openNewPymol(ap, new PDBEntry[] { pdb },
48             new SequenceI[][]
49             { seqs });
50   }
51
52   public PymolViewer(PDBEntry[] pe, boolean alignAdded, SequenceI[][] seqs,
53           AlignmentPanel ap)
54   {
55     this();
56     setAlignAddedStructures(alignAdded);
57     openNewPymol(ap, pe, seqs);
58   }
59
60   private void openNewPymol(AlignmentPanel ap, PDBEntry[] pe,
61           SequenceI[][] seqs)
62   {
63     createProgressBar();
64     binding = new PymolBindingModel(this, ap.getStructureSelectionManager(),
65             pe, seqs);
66     addAlignmentPanel(ap);
67     useAlignmentPanelForColourbyseq(ap);
68
69     if (pe.length > 1)
70     {
71       useAlignmentPanelForSuperposition(ap);
72     }
73     binding.setColourBySequence(true);
74     setSize(myWidth, myHeight);
75     initMenus();
76
77     addingStructures = false;
78     worker = new Thread(this);
79     worker.start();
80
81     this.addInternalFrameListener(new InternalFrameAdapter()
82     {
83       @Override
84       public void internalFrameClosing(
85               InternalFrameEvent internalFrameEvent)
86       {
87         closeViewer(false);
88       }
89     });
90
91   }
92
93   /**
94    * Create a helper to manage progress bar display
95    */
96   protected void createProgressBar()
97   {
98     if (getProgressIndicator() == null)
99     {
100       setProgressIndicator(new ProgressBar(statusPanel, statusBar));
101     }
102   }
103
104   @Override
105   public void run()
106   {
107     // todo pull up much of this
108   
109     StringBuilder errormsgs = new StringBuilder(128);
110     List<PDBEntry> filePDB = new ArrayList<>();
111     List<Integer> filePDBpos = new ArrayList<>();
112     String[] curfiles = binding.getStructureFiles(); // files currently in viewer
113     for (int pi = 0; pi < binding.getPdbCount(); pi++)
114     {
115       String file = null;
116       PDBEntry thePdbEntry = binding.getPdbEntry(pi);
117       if (thePdbEntry.getFile() == null)
118       {
119         /*
120          * Retrieve PDB data, save to file, attach to PDBEntry
121          */
122         file = fetchPdbFile(thePdbEntry);
123         if (file == null)
124         {
125           errormsgs.append("'" + thePdbEntry.getId() + "' ");
126         }
127       }
128       else
129       {
130         /*
131          * got file already
132          */
133         file = new File(thePdbEntry.getFile()).getAbsoluteFile()
134                 .getPath();
135         // todo - skip if already loaded in PyMOL
136       }
137       if (file != null)
138       {
139         filePDB.add(thePdbEntry);
140         filePDBpos.add(Integer.valueOf(pi));
141       }
142     }
143         
144     if (!filePDB.isEmpty())
145     {
146       /*
147        * at least one structure to add to viewer
148        */
149       binding.setFinishedInit(false);
150       if (!addingStructures)
151       {
152         try
153         {
154           initPymol();
155         } catch (Exception ex)
156         {
157           Cache.log.error("Couldn't open PyMOL viewer!", ex);
158         }
159       }
160       int num = -1;
161       for (PDBEntry pe : filePDB)
162       {
163         num++;
164         if (pe.getFile() != null)
165         {
166           try
167           {
168             int pos = filePDBpos.get(num).intValue();
169             long startTime = startProgressBar(getViewerName() + " "
170                     + MessageManager.getString("status.opening_file_for")
171                     + " " + pe.getId());
172             binding.openFile(pe);
173             binding.addSequence(pos, binding.getSequence()[pos]);
174             File fl = new File(pe.getFile());
175             DataSourceType protocol = DataSourceType.URL;
176             try
177             {
178               if (fl.exists())
179               {
180                 protocol = DataSourceType.FILE;
181               }
182             } catch (Throwable e)
183             {
184             } finally
185             {
186               stopProgressBar("", startTime);
187             }
188
189             StructureFile pdb = binding.getSsm().setMapping(
190                     binding.getSequence()[pos], binding.getChains()[pos],
191                     pe.getFile(), protocol,
192                     getProgressIndicator());
193             binding.stashFoundChains(pdb, pe.getFile());
194           } catch (Exception ex)
195           {
196             Cache.log.error(
197                     "Couldn't open " + pe.getFile() + " in Chimera viewer!",
198                     ex);
199           } finally
200           {
201             // Cache.log.debug("File locations are " + files);
202           }
203         }
204       }
205
206       binding.refreshGUI();
207       binding.setFinishedInit(true);
208       binding.setLoadingFromArchive(false);
209
210       /*
211        * ensure that any newly discovered features (e.g. RESNUM)
212        * are added to any open feature settings dialog
213        */
214       FeatureRenderer fr = getBinding().getFeatureRenderer(null);
215       if (fr != null)
216       {
217         fr.featuresAdded();
218       }
219
220       // refresh the sequence colours for the new structure(s)
221       for (AlignmentViewPanel ap : _colourwith)
222       {
223         binding.updateColours(ap);
224       }
225       // do superposition if asked to
226       if (alignAddedStructures)
227       {
228         new Thread(new Runnable()
229         {
230           @Override
231           public void run()
232           {
233             alignStructsWithAllAlignPanels();
234           }
235         }).start();
236       }
237       addingStructures = false;
238     }
239     _started = false;
240     worker = null;
241
242   }
243
244   /**
245    * Launch PyMOL. If we have a session file name, send PyMOL the command to
246    * open its saved session file.
247    */
248   void initPymol()
249   {
250     Desktop.addInternalFrame(this,
251             binding.getViewerTitle(getViewerName(), true),
252             getBounds().width, getBounds().height);
253
254     if (!binding.launchPymol())
255     {
256       JvOptionPane.showMessageDialog(Desktop.desktop,
257               MessageManager.getString("label.pymol_failed"),
258               MessageManager.getString("label.error_loading_file"),
259               JvOptionPane.ERROR_MESSAGE);
260       this.dispose();
261       return;
262     }
263
264     if (this.pymolSessionFile != null)
265     {
266       boolean opened = binding.openSession(pymolSessionFile);
267       if (!opened)
268       {
269         System.err.println("An error occurred opening PyMOL session file "
270                 + pymolSessionFile);
271       }
272     }
273     // binding.startPymolListener();
274   }
275
276   @Override
277   public AAStructureBindingModel getBinding()
278   {
279     return binding;
280   }
281
282   @Override
283   public void closeViewer(boolean closePymol)
284   {
285     if (binding != null && binding.isPymolRunning())
286     {
287       if (!closePymol)
288       {
289         // TODO i18n (and pull up)
290         String prompt = MessageManager
291                 .formatMessage("label.confirm_close_pymol", new Object[]
292                 { binding.getViewerTitle(getViewerName(), false) });
293         prompt = JvSwingUtils.wrapTooltip(true, prompt);
294         int confirm = JvOptionPane.showConfirmDialog(this, prompt,
295                 MessageManager.getString("label.close_viewer"),
296                 JvOptionPane.YES_NO_CANCEL_OPTION);
297         /*
298          * abort closure if user hits escape or Cancel
299          */
300         if (confirm == JvOptionPane.CANCEL_OPTION
301                 || confirm == JvOptionPane.CLOSED_OPTION)
302         {
303           return;
304         }
305         closePymol = confirm == JvOptionPane.YES_OPTION;
306       }
307       binding.closeViewer(closePymol);
308     }
309     setAlignmentPanel(null);
310     _aps.clear();
311     _alignwith.clear();
312     _colourwith.clear();
313     // TODO: check for memory leaks where instance isn't finalised because
314     // binding
315     // holds a reference to the window
316     binding = null;
317     dispose();
318   }
319
320   @Override
321   public String getStateInfo()
322   {
323     return null;
324   }
325
326   @Override
327   public ViewerType getViewerType()
328   {
329     return ViewerType.PYMOL;
330   }
331
332   @Override
333   protected String getViewerName()
334   {
335     return "PyMOL";
336   }
337
338 }