JAL-1953 2.11.2 with Archeopteryx!
[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 import java.util.Map;
32
33 import javax.swing.JInternalFrame;
34 import javax.swing.JMenu;
35 import javax.swing.JMenuItem;
36 import javax.swing.event.InternalFrameAdapter;
37 import javax.swing.event.InternalFrameEvent;
38
39 import jalview.api.AlignmentViewPanel;
40 import jalview.api.FeatureRenderer;
41 import jalview.bin.Console;
42 import jalview.datamodel.PDBEntry;
43 import jalview.datamodel.SequenceI;
44 import jalview.datamodel.StructureViewerModel;
45 import jalview.datamodel.StructureViewerModel.StructureData;
46 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
47 import jalview.gui.StructureViewer.ViewerType;
48 import jalview.io.DataSourceType;
49 import jalview.io.StructureFile;
50 import jalview.structures.models.AAStructureBindingModel;
51 import jalview.util.ImageMaker.TYPE;
52 import jalview.util.MessageManager;
53 import jalview.util.Platform;
54
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    * Path to Chimera session file. This is set when an open Jalview/Chimera
67    * session is saved, or on restore from a Jalview project (if it holds the
68    * filename of any saved Chimera sessions).
69    */
70   private String chimeraSessionFile = null;
71
72   private int myWidth = 500;
73
74   private int myHeight = 150;
75
76   private JMenuItem writeFeatures = null;
77
78   private JMenu fetchAttributes = null;
79
80   /**
81    * Initialise menu options.
82    */
83   @Override
84   protected void initMenus()
85   {
86     super.initMenus();
87
88     savemenu.setVisible(false); // not yet implemented
89     viewMenu.add(fitToWindow);
90
91     writeFeatures = new JMenuItem(
92             MessageManager.getString("label.create_viewer_attributes"));
93     writeFeatures.setToolTipText(
94             MessageManager.getString("label.create_viewer_attributes_tip"));
95     writeFeatures.addActionListener(new ActionListener()
96     {
97       @Override
98       public void actionPerformed(ActionEvent e)
99       {
100         sendFeaturesToChimera();
101       }
102     });
103     viewerActionMenu.add(writeFeatures);
104
105     fetchAttributes = new JMenu(MessageManager.formatMessage(
106             "label.fetch_viewer_attributes", getViewerName()));
107     fetchAttributes.setToolTipText(MessageManager.formatMessage(
108             "label.fetch_viewer_attributes_tip", getViewerName()));
109     fetchAttributes.addMouseListener(new MouseAdapter()
110     {
111
112       @Override
113       public void mouseEntered(MouseEvent e)
114       {
115         buildAttributesMenu(fetchAttributes);
116       }
117     });
118     viewerActionMenu.add(fetchAttributes);
119   }
120
121   @Override
122   protected void buildActionMenu()
123   {
124     super.buildActionMenu();
125     // add these back in after menu is refreshed
126     viewerActionMenu.add(writeFeatures);
127     viewerActionMenu.add(fetchAttributes);
128
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(MessageManager.formatMessage("label.attributes_set",
170             count, getViewerName()));
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, "OpenChimera");
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   /**
242    * Create a new viewer from saved session state data including Chimera session
243    * file
244    * 
245    * @param chimeraSessionFile
246    * @param alignPanel
247    * @param pdbArray
248    * @param seqsArray
249    * @param colourByChimera
250    * @param colourBySequence
251    * @param newViewId
252    */
253   public ChimeraViewFrame(StructureViewerModel viewerData,
254           AlignmentPanel alignPanel, String sessionFile, String vid)
255   {
256     this();
257     setViewId(vid);
258     this.chimeraSessionFile = sessionFile;
259     Map<File, StructureData> pdbData = viewerData.getFileData();
260     PDBEntry[] pdbArray = new PDBEntry[pdbData.size()];
261     SequenceI[][] seqsArray = new SequenceI[pdbData.size()][];
262     int i = 0;
263     for (StructureData data : pdbData.values())
264     {
265       PDBEntry pdbentry = new PDBEntry(data.getPdbId(), null,
266               PDBEntry.Type.PDB, data.getFilePath());
267       pdbArray[i] = pdbentry;
268       List<SequenceI> sequencesForPdb = data.getSeqList();
269       seqsArray[i] = sequencesForPdb
270               .toArray(new SequenceI[sequencesForPdb.size()]);
271       i++;
272     }
273     openNewChimera(alignPanel, pdbArray, seqsArray);
274     if (viewerData.isColourByViewer())
275     {
276       jmb.setColourBySequence(false);
277       seqColour.setSelected(false);
278       viewerColour.setSelected(true);
279     }
280     else if (viewerData.isColourWithAlignPanel())
281     {
282       jmb.setColourBySequence(true);
283       seqColour.setSelected(true);
284       viewerColour.setSelected(false);
285     }
286   }
287
288   /**
289    * create a new viewer containing several structures, optionally superimposed
290    * using the given alignPanel.
291    * 
292    * @param pe
293    * @param seqs
294    * @param ap
295    */
296   public ChimeraViewFrame(PDBEntry[] pe, boolean alignAdded,
297           SequenceI[][] seqs, 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.desktop,
332               MessageManager.formatMessage("label.open_viewer_failed",
333                       getViewerName()),
334               MessageManager.getString("label.error_loading_file"),
335               JvOptionPane.ERROR_MESSAGE);
336       jmb.closeViewer(true);
337       this.dispose();
338       return;
339     }
340
341     if (this.chimeraSessionFile != null)
342     {
343       boolean opened = jmb.openSession(chimeraSessionFile);
344       if (!opened)
345       {
346         System.err.println("An error occurred opening Chimera session file "
347                 + chimeraSessionFile);
348       }
349     }
350
351     jmb.startChimeraListener();
352   }
353
354   /**
355    * Open any newly added PDB structures in Chimera, having first fetched data
356    * from PDB (if not already saved).
357    */
358   @Override
359   public void run()
360   {
361     _started = true;
362     // todo - record which pdbids were successfully imported.
363     StringBuilder errormsgs = new StringBuilder(128);
364     StringBuilder files = new StringBuilder(128);
365     List<PDBEntry> filePDB = new ArrayList<>();
366     List<Integer> filePDBpos = new ArrayList<>();
367     PDBEntry thePdbEntry = null;
368     StructureFile pdb = null;
369     try
370     {
371       String[] curfiles = jmb.getStructureFiles(); // files currently in viewer
372       // TODO: replace with reference fetching/transfer code (validate PDBentry
373       // as a DBRef?)
374       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
375       {
376         String file = null;
377         thePdbEntry = jmb.getPdbEntry(pi);
378         if (thePdbEntry.getFile() == null)
379         {
380           /*
381            * Retrieve PDB data, save to file, attach to PDBEntry
382            */
383           file = fetchPdbFile(thePdbEntry);
384           if (file == null)
385           {
386             errormsgs.append("'" + thePdbEntry.getId() + "' ");
387           }
388         }
389         else
390         {
391           /*
392            * Got file already - ignore if already loaded in Chimera.
393            */
394           file = new File(thePdbEntry.getFile()).getAbsoluteFile()
395                   .getPath();
396           if (curfiles != null && curfiles.length > 0)
397           {
398             addingStructures = true; // already files loaded.
399             for (int c = 0; c < curfiles.length; c++)
400             {
401               if (curfiles[c].equals(file))
402               {
403                 file = null;
404                 break;
405               }
406             }
407           }
408         }
409         if (file != null)
410         {
411           filePDB.add(thePdbEntry);
412           filePDBpos.add(Integer.valueOf(pi));
413           files.append(" \"" + Platform.escapeBackslashes(file) + "\"");
414         }
415       }
416     } catch (OutOfMemoryError oomerror)
417     {
418       new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
419               oomerror);
420     } catch (Exception ex)
421     {
422       ex.printStackTrace();
423       errormsgs.append(
424               "When retrieving pdbfiles for '" + thePdbEntry.getId() + "'");
425     }
426     if (errormsgs.length() > 0)
427     {
428
429       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
430               MessageManager.formatMessage(
431                       "label.pdb_entries_couldnt_be_retrieved", new Object[]
432                       { errormsgs.toString() }),
433               MessageManager.getString("label.couldnt_load_file"),
434               JvOptionPane.ERROR_MESSAGE);
435     }
436
437     if (files.length() > 0)
438     {
439       jmb.setFinishedInit(false);
440       if (!addingStructures)
441       {
442         try
443         {
444           initChimera();
445         } catch (Exception ex)
446         {
447           Console.error("Couldn't open Chimera viewer!", ex);
448         }
449       }
450       if (!jmb.isViewerRunning())
451       {
452         // nothing to do
453         // TODO: ensure we tidy up JAL-3619
454         return;
455       }
456       int num = -1;
457       for (PDBEntry pe : filePDB)
458       {
459         num++;
460         if (pe.getFile() != null)
461         {
462           try
463           {
464             int pos = filePDBpos.get(num).intValue();
465             long startTime = startProgressBar(getViewerName() + " "
466                     + MessageManager.getString("status.opening_file_for")
467                     + " " + pe.getId());
468             jmb.openFile(pe);
469             jmb.addSequence(pos, jmb.getSequence()[pos]);
470             File fl = new File(pe.getFile());
471             DataSourceType protocol = DataSourceType.URL;
472             try
473             {
474               if (fl.exists())
475               {
476                 protocol = DataSourceType.FILE;
477               }
478             } catch (Throwable e)
479             {
480             } finally
481             {
482               stopProgressBar("", startTime);
483             }
484             // Explicitly map to the filename used by Chimera ;
485
486             pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
487                     jmb.getChains()[pos], pe.getFile(), protocol,
488                     getProgressIndicator());
489             jmb.stashFoundChains(pdb, pe.getFile());
490
491           } catch (OutOfMemoryError oomerror)
492           {
493             new OOMWarning(
494                     "When trying to open and map structures from Chimera!",
495                     oomerror);
496           } catch (Exception ex)
497           {
498             Console.error(
499                     "Couldn't open " + pe.getFile() + " in Chimera viewer!",
500                     ex);
501           } finally
502           {
503             Console.debug("File locations are " + files);
504           }
505         }
506       }
507
508       jmb.refreshGUI();
509       jmb.setFinishedInit(true);
510       jmb.setLoadingFromArchive(false);
511
512       /*
513        * ensure that any newly discovered features (e.g. RESNUM)
514        * are notified to the FeatureRenderer (and added to any 
515        * open feature settings dialog)
516        */
517       FeatureRenderer fr = getBinding().getFeatureRenderer(null);
518       if (fr != null)
519       {
520         fr.featuresAdded();
521       }
522
523       // refresh the sequence colours for the new structure(s)
524       for (AlignmentViewPanel ap : _colourwith)
525       {
526         jmb.updateColours(ap);
527       }
528       // do superposition if asked to
529       if (alignAddedStructures)
530       {
531         new Thread(new Runnable()
532         {
533           @Override
534           public void run()
535           {
536             alignStructsWithAllAlignPanels();
537           }
538         }).start();
539       }
540       addingStructures = false;
541     }
542     _started = false;
543     worker = null;
544   }
545
546   @Override
547   public void makePDBImage(TYPE imageType)
548   {
549     throw new UnsupportedOperationException(
550             "Image export for Chimera is not implemented");
551   }
552
553   @Override
554   public AAStructureBindingModel getBinding()
555   {
556     return jmb;
557   }
558
559   @Override
560   public ViewerType getViewerType()
561   {
562     return ViewerType.CHIMERA;
563   }
564
565   @Override
566   protected String getViewerName()
567   {
568     return "Chimera";
569   }
570 }