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