65b002b0a4e90b83e91da1d88188012e4c239c66
[jalview.git] / src / jalview / gui / ChimeraViewFrame.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.awt.event.MouseAdapter;
26 import java.awt.event.MouseEvent;
27 import java.io.File;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.List;
31
32 import javax.swing.JInternalFrame;
33 import javax.swing.JMenu;
34 import javax.swing.JMenuItem;
35 import javax.swing.event.InternalFrameAdapter;
36 import javax.swing.event.InternalFrameEvent;
37
38 import jalview.api.AlignmentViewPanel;
39 import jalview.api.FeatureRenderer;
40 import jalview.bin.Cache;
41 import jalview.datamodel.PDBEntry;
42 import jalview.datamodel.SequenceI;
43 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
44 import jalview.gui.StructureViewer.ViewerType;
45 import jalview.io.DataSourceType;
46 import jalview.io.StructureFile;
47 import jalview.structures.models.AAStructureBindingModel;
48 import jalview.util.ImageMaker.TYPE;
49 import jalview.util.MessageManager;
50 import jalview.util.Platform;
51
52 /**
53  * GUI elements for handling an external chimera display
54  * 
55  * @author jprocter
56  *
57  */
58 public class ChimeraViewFrame extends StructureViewerBase
59 {
60   private JalviewChimeraBinding jmb;
61
62   /*
63    * Path to Chimera session file. This is set when an open Jalview/Chimera
64    * session is saved, or on restore from a Jalview project (if it holds the
65    * filename of any saved Chimera sessions).
66    */
67   private String chimeraSessionFile = null;
68
69   private int myWidth = 500;
70
71   private int myHeight = 150;
72
73   /**
74    * Initialise menu options.
75    */
76   @Override
77   protected void initMenus()
78   {
79     super.initMenus();
80
81     savemenu.setVisible(false); // not yet implemented
82     viewMenu.add(fitToWindow);
83
84     JMenuItem writeFeatures = new JMenuItem(
85             MessageManager.getString("label.create_viewer_attributes"));
86     writeFeatures.setToolTipText(MessageManager
87             .getString("label.create_viewer_attributes_tip"));
88     writeFeatures.addActionListener(new ActionListener()
89     {
90       @Override
91       public void actionPerformed(ActionEvent e)
92       {
93         sendFeaturesToChimera();
94       }
95     });
96     viewerActionMenu.add(writeFeatures);
97
98     final JMenu fetchAttributes = new JMenu(
99             MessageManager.getString("label.fetch_chimera_attributes"));
100     fetchAttributes.setToolTipText(
101             MessageManager.getString("label.fetch_chimera_attributes_tip"));
102     fetchAttributes.addMouseListener(new MouseAdapter()
103     {
104
105       @Override
106       public void mouseEntered(MouseEvent e)
107       {
108         buildAttributesMenu(fetchAttributes);
109       }
110     });
111     viewerActionMenu.add(fetchAttributes);
112   }
113
114   /**
115    * Query Chimera for its residue attribute names and add them as items off the
116    * attributes menu
117    * 
118    * @param attributesMenu
119    */
120   protected void buildAttributesMenu(JMenu attributesMenu)
121   {
122     List<String> atts = jmb.getChimeraAttributes();
123     attributesMenu.removeAll();
124     Collections.sort(atts);
125     for (String attName : atts)
126     {
127       JMenuItem menuItem = new JMenuItem(attName);
128       menuItem.addActionListener(new ActionListener()
129       {
130         @Override
131         public void actionPerformed(ActionEvent e)
132         {
133           getChimeraAttributes(attName);
134         }
135       });
136       attributesMenu.add(menuItem);
137     }
138   }
139
140   /**
141    * Read residues in Chimera with the given attribute name, and set as features
142    * on the corresponding sequence positions (if any)
143    * 
144    * @param attName
145    */
146   protected void getChimeraAttributes(String attName)
147   {
148     jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel());
149   }
150
151   /**
152    * Sends command(s) to the structure viewer to create residue attributes for
153    * visible Jalview features
154    */
155   protected void sendFeaturesToChimera()
156   {
157     // todo pull up?
158     int count = jmb.sendFeaturesToViewer(getAlignmentPanel());
159     statusBar.setText(
160             MessageManager.formatMessage("label.attributes_set", count));
161   }
162
163   /**
164    * open a single PDB structure in a new Chimera view
165    * 
166    * @param pdbentry
167    * @param seq
168    * @param chains
169    * @param ap
170    */
171   public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
172           String[] chains, final AlignmentPanel ap)
173   {
174     this();
175
176     openNewChimera(ap, new PDBEntry[] { pdbentry },
177             new SequenceI[][]
178             { seq });
179   }
180
181   /**
182    * Create a helper to manage progress bar display
183    */
184   protected void createProgressBar()
185   {
186     if (getProgressIndicator() == null)
187     {
188       setProgressIndicator(new ProgressBar(statusPanel, statusBar));
189     }
190   }
191
192   private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
193           SequenceI[][] seqs)
194   {
195     createProgressBar();
196     jmb = newBindingModel(ap, pdbentrys, seqs);
197     addAlignmentPanel(ap);
198     useAlignmentPanelForColourbyseq(ap);
199
200     if (pdbentrys.length > 1)
201     {
202       useAlignmentPanelForSuperposition(ap);
203     }
204     jmb.setColourBySequence(true);
205     setSize(myWidth, myHeight);
206     initMenus();
207
208     addingStructures = false;
209     worker = new Thread(this);
210     worker.start();
211
212     this.addInternalFrameListener(new InternalFrameAdapter()
213     {
214       @Override
215       public void internalFrameClosing(
216               InternalFrameEvent internalFrameEvent)
217       {
218         closeViewer(false);
219       }
220     });
221
222   }
223
224   protected JalviewChimeraBindingModel newBindingModel(AlignmentPanel ap,
225           PDBEntry[] pdbentrys, SequenceI[][] seqs)
226   {
227     return new JalviewChimeraBindingModel(this,
228             ap.getStructureSelectionManager(), pdbentrys, seqs, null);
229   }
230
231   /**
232    * Create a new viewer from saved session state data including Chimera session
233    * file
234    * 
235    * @param chimeraSessionFile
236    * @param alignPanel
237    * @param pdbArray
238    * @param seqsArray
239    * @param colourByChimera
240    * @param colourBySequence
241    * @param newViewId
242    */
243   public ChimeraViewFrame(String chimeraSessionFile,
244           AlignmentPanel alignPanel, PDBEntry[] pdbArray,
245           SequenceI[][] seqsArray, boolean colourByChimera,
246           boolean colourBySequence, String newViewId)
247   {
248     this();
249     setViewId(newViewId);
250     this.chimeraSessionFile = chimeraSessionFile;
251     openNewChimera(alignPanel, pdbArray, seqsArray);
252     if (colourByChimera)
253     {
254       jmb.setColourBySequence(false);
255       seqColour.setSelected(false);
256       viewerColour.setSelected(true);
257     }
258     else if (colourBySequence)
259     {
260       jmb.setColourBySequence(true);
261       seqColour.setSelected(true);
262       viewerColour.setSelected(false);
263     }
264   }
265
266   /**
267    * create a new viewer containing several structures, optionally superimposed
268    * using the given alignPanel.
269    * 
270    * @param pe
271    * @param seqs
272    * @param ap
273    */
274   public ChimeraViewFrame(PDBEntry[] pe, boolean alignAdded,
275           SequenceI[][] seqs,
276           AlignmentPanel ap)
277   {
278     this();
279     setAlignAddedStructures(alignAdded);
280     openNewChimera(ap, pe, seqs);
281   }
282
283   /**
284    * Default constructor
285    */
286   public ChimeraViewFrame()
287   {
288     super();
289
290     /*
291      * closeViewer will decide whether or not to close this frame
292      * depending on whether user chooses to Cancel or not
293      */
294     setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
295   }
296
297   /**
298    * Launch Chimera. If we have a chimera session file name, send Chimera the
299    * command to open its saved session file.
300    */
301   void initChimera()
302   {
303     jmb.setFinishedInit(false);
304     Desktop.addInternalFrame(this,
305             jmb.getViewerTitle(getViewerName(), true), getBounds().width,
306             getBounds().height);
307
308     if (!jmb.launchChimera())
309     {
310       JvOptionPane.showMessageDialog(Desktop.desktop,
311               MessageManager.formatMessage("label.open_viewer_failed",
312                       getViewerName()),
313               MessageManager.getString("label.error_loading_file"),
314               JvOptionPane.ERROR_MESSAGE);
315       this.dispose();
316       return;
317     }
318
319     if (this.chimeraSessionFile != null)
320     {
321       boolean opened = jmb.openSession(chimeraSessionFile);
322       if (!opened)
323       {
324         System.err.println("An error occurred opening Chimera session file "
325                 + chimeraSessionFile);
326       }
327     }
328
329     jmb.startChimeraListener();
330   }
331
332   /**
333    * Open any newly added PDB structures in Chimera, having first fetched data
334    * from PDB (if not already saved).
335    */
336   @Override
337   public void run()
338   {
339     _started = true;
340     // todo - record which pdbids were successfully imported.
341     StringBuilder errormsgs = new StringBuilder(128);
342     StringBuilder files = new StringBuilder(128);
343     List<PDBEntry> filePDB = new ArrayList<>();
344     List<Integer> filePDBpos = new ArrayList<>();
345     PDBEntry thePdbEntry = null;
346     StructureFile pdb = null;
347     try
348     {
349       String[] curfiles = jmb.getStructureFiles(); // files currently in viewer
350       // TODO: replace with reference fetching/transfer code (validate PDBentry
351       // as a DBRef?)
352       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
353       {
354         String file = null;
355         thePdbEntry = jmb.getPdbEntry(pi);
356         if (thePdbEntry.getFile() == null)
357         {
358           /*
359            * Retrieve PDB data, save to file, attach to PDBEntry
360            */
361           file = fetchPdbFile(thePdbEntry);
362           if (file == null)
363           {
364             errormsgs.append("'" + thePdbEntry.getId() + "' ");
365           }
366         }
367         else
368         {
369           /*
370            * Got file already - ignore if already loaded in Chimera.
371            */
372           file = new File(thePdbEntry.getFile()).getAbsoluteFile()
373                   .getPath();
374           if (curfiles != null && curfiles.length > 0)
375           {
376             addingStructures = true; // already files loaded.
377             for (int c = 0; c < curfiles.length; c++)
378             {
379               if (curfiles[c].equals(file))
380               {
381                 file = null;
382                 break;
383               }
384             }
385           }
386         }
387         if (file != null)
388         {
389           filePDB.add(thePdbEntry);
390           filePDBpos.add(Integer.valueOf(pi));
391           files.append(" \"" + Platform.escapeBackslashes(file) + "\"");
392         }
393       }
394     } catch (OutOfMemoryError oomerror)
395     {
396       new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
397               oomerror);
398     } catch (Exception ex)
399     {
400       ex.printStackTrace();
401       errormsgs.append(
402               "When retrieving pdbfiles for '" + thePdbEntry.getId() + "'");
403     }
404     if (errormsgs.length() > 0)
405     {
406
407       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
408               MessageManager.formatMessage(
409                       "label.pdb_entries_couldnt_be_retrieved", new Object[]
410                       { errormsgs.toString() }),
411               MessageManager.getString("label.couldnt_load_file"),
412               JvOptionPane.ERROR_MESSAGE);
413     }
414
415     if (files.length() > 0)
416     {
417       jmb.setFinishedInit(false);
418       if (!addingStructures)
419       {
420         try
421         {
422           initChimera();
423         } catch (Exception ex)
424         {
425           Cache.log.error("Couldn't open Chimera viewer!", ex);
426         }
427       }
428       int num = -1;
429       for (PDBEntry pe : filePDB)
430       {
431         num++;
432         if (pe.getFile() != null)
433         {
434           try
435           {
436             int pos = filePDBpos.get(num).intValue();
437             long startTime = startProgressBar(getViewerName() + " "
438                     + MessageManager.getString("status.opening_file_for")
439                     + " " + pe.getId());
440             jmb.openFile(pe);
441             jmb.addSequence(pos, jmb.getSequence()[pos]);
442             File fl = new File(pe.getFile());
443             DataSourceType protocol = DataSourceType.URL;
444             try
445             {
446               if (fl.exists())
447               {
448                 protocol = DataSourceType.FILE;
449               }
450             } catch (Throwable e)
451             {
452             } finally
453             {
454               stopProgressBar("", startTime);
455             }
456             // Explicitly map to the filename used by Chimera ;
457
458             pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
459                     jmb.getChains()[pos], pe.getFile(), protocol,
460                     getProgressIndicator());
461             jmb.stashFoundChains(pdb, pe.getFile());
462
463           } catch (OutOfMemoryError oomerror)
464           {
465             new OOMWarning(
466                     "When trying to open and map structures from Chimera!",
467                     oomerror);
468           } catch (Exception ex)
469           {
470             Cache.log.error(
471                     "Couldn't open " + pe.getFile() + " in Chimera viewer!",
472                     ex);
473           } finally
474           {
475             Cache.log.debug("File locations are " + files);
476           }
477         }
478       }
479
480       jmb.refreshGUI();
481       jmb.setFinishedInit(true);
482       jmb.setLoadingFromArchive(false);
483
484       /*
485        * ensure that any newly discovered features (e.g. RESNUM)
486        * are added to any open feature settings dialog
487        */
488       FeatureRenderer fr = getBinding().getFeatureRenderer(null);
489       if (fr != null)
490       {
491         fr.featuresAdded();
492       }
493
494       // refresh the sequence colours for the new structure(s)
495       for (AlignmentViewPanel ap : _colourwith)
496       {
497         jmb.updateColours(ap);
498       }
499       // do superposition if asked to
500       if (alignAddedStructures)
501       {
502         new Thread(new Runnable()
503         {
504           @Override
505           public void run()
506           {
507             alignStructsWithAllAlignPanels();
508           }
509         }).start();
510       }
511       addingStructures = false;
512     }
513     _started = false;
514     worker = null;
515   }
516
517   @Override
518   public void makePDBImage(TYPE imageType)
519   {
520     throw new UnsupportedOperationException(
521             "Image export for Chimera is not implemented");
522   }
523
524   @Override
525   public AAStructureBindingModel getBinding()
526   {
527     return jmb;
528   }
529
530   @Override
531   protected void fitToWindow_actionPerformed()
532   {
533     jmb.focusView();
534   }
535
536   @Override
537   public ViewerType getViewerType()
538   {
539     return ViewerType.CHIMERA;
540   }
541
542   @Override
543   protected String getViewerName()
544   {
545     return "Chimera";
546   }
547 }