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