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