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