JAL-1588 refactoring to StructureViewerBase and related
[jalview.git] / src / jalview / gui / ChimeraViewFrame.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 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 jalview.bin.Cache;
24 import jalview.datamodel.Alignment;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.ColumnSelection;
27 import jalview.datamodel.PDBEntry;
28 import jalview.datamodel.SequenceI;
29 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
30 import jalview.io.AppletFormatAdapter;
31 import jalview.io.JalviewFileChooser;
32 import jalview.io.JalviewFileView;
33 import jalview.schemes.BuriedColourScheme;
34 import jalview.schemes.ColourSchemeI;
35 import jalview.schemes.HelixColourScheme;
36 import jalview.schemes.HydrophobicColourScheme;
37 import jalview.schemes.PurinePyrimidineColourScheme;
38 import jalview.schemes.StrandColourScheme;
39 import jalview.schemes.TaylorColourScheme;
40 import jalview.schemes.TurnColourScheme;
41 import jalview.schemes.ZappoColourScheme;
42 import jalview.structures.models.AAStructureBindingModel;
43 import jalview.util.MessageManager;
44 import jalview.util.Platform;
45 import jalview.ws.dbsources.Pdb;
46
47 import java.awt.event.ActionEvent;
48 import java.awt.event.ActionListener;
49 import java.awt.event.ItemEvent;
50 import java.awt.event.ItemListener;
51 import java.io.BufferedReader;
52 import java.io.File;
53 import java.io.FileOutputStream;
54 import java.io.FileReader;
55 import java.io.IOException;
56 import java.io.PrintWriter;
57 import java.util.ArrayList;
58 import java.util.List;
59 import java.util.Vector;
60
61 import javax.swing.JCheckBoxMenuItem;
62 import javax.swing.JColorChooser;
63 import javax.swing.JInternalFrame;
64 import javax.swing.JMenu;
65 import javax.swing.JMenuItem;
66 import javax.swing.JOptionPane;
67 import javax.swing.event.InternalFrameAdapter;
68 import javax.swing.event.InternalFrameEvent;
69 import javax.swing.event.MenuEvent;
70 import javax.swing.event.MenuListener;
71
72 /**
73  * GUI elements for handlnig an external chimera display
74  * 
75  * @author jprocter
76  *
77  */
78 public class ChimeraViewFrame extends StructureViewerBase
79 {
80   private JalviewChimeraBinding jmb;
81
82   private boolean allChainsSelected = false;
83
84   private boolean alignAddedStructures = false;
85
86   /*
87    * state flag for PDB retrieval thread
88    */
89   private boolean _started = false;
90
91   private boolean addingStructures = false;
92
93   private IProgressIndicator progressBar = null;
94
95   /*
96    * pdb retrieval thread.
97    */
98   private Thread worker = null;
99
100   /**
101    * Initialise menu options.
102    */
103   private void initMenus()
104   {
105     viewerActionMenu.setText(MessageManager.getString("label.chimera"));
106     viewerColour.setText(MessageManager
107             .getString("label.colour_with_chimera"));
108     viewerColour.setToolTipText(MessageManager
109             .getString("label.let_chimera_manage_structure_colours"));
110     helpItem.setText(MessageManager.getString("label.chimera_help"));
111     seqColour.setSelected(jmb.isColourBySequence());
112     viewerColour.setSelected(!jmb.isColourBySequence());
113     if (_colourwith == null)
114     {
115       _colourwith = new Vector<AlignmentPanel>();
116     }
117     if (_alignwith == null)
118     {
119       _alignwith = new Vector<AlignmentPanel>();
120     }
121
122     // save As not yet implemented
123     savemenu.setVisible(false);
124
125     ViewSelectionMenu seqColourBy = new ViewSelectionMenu(
126             MessageManager.getString("label.colour_by"), this, _colourwith,
127             new ItemListener()
128             {
129               @Override
130               public void itemStateChanged(ItemEvent e)
131               {
132                 if (!seqColour.isSelected())
133                 {
134                   seqColour.doClick();
135                 }
136                 else
137                 {
138                   // update the Chimera display now.
139                   seqColour_actionPerformed(null);
140                 }
141               }
142             });
143     viewMenu.add(seqColourBy);
144     final ItemListener handler;
145     JMenu alpanels = new ViewSelectionMenu(
146             MessageManager.getString("label.superpose_with"), this,
147             _alignwith, handler = new ItemListener()
148             {
149               @Override
150               public void itemStateChanged(ItemEvent e)
151               {
152                 alignStructs.setEnabled(_alignwith.size() > 0);
153                 alignStructs.setToolTipText(MessageManager
154                         .formatMessage(
155                                 "label.align_structures_using_linked_alignment_views",
156                                 new Object[]
157                                 { new Integer(_alignwith.size()).toString() }));
158               }
159             });
160     handler.itemStateChanged(null);
161     viewerActionMenu.add(alpanels);
162     viewerActionMenu.addMenuListener(new MenuListener()
163     {
164
165       @Override
166       public void menuSelected(MenuEvent e)
167       {
168         handler.itemStateChanged(null);
169       }
170
171       @Override
172       public void menuDeselected(MenuEvent e)
173       {
174         // TODO Auto-generated method stub
175       }
176
177       @Override
178       public void menuCanceled(MenuEvent e)
179       {
180         // TODO Auto-generated method stub
181       }
182     });
183   }
184
185   /**
186    * add a single PDB structure to a new or existing Chimera view
187    * 
188    * @param pdbentry
189    * @param seq
190    * @param chains
191    * @param ap
192    */
193   public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
194           String[] chains, final AlignmentPanel ap)
195   {
196     super();
197     progressBar = ap.alignFrame;
198     // ////////////////////////////////
199     // Is the pdb file already loaded?
200     String alreadyMapped = ap.getStructureSelectionManager()
201             .alreadyMappedToFile(pdbentry.getId());
202
203     if (alreadyMapped != null)
204     {
205       int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
206               MessageManager.formatMessage(
207                       "label.pdb_entry_is_already_displayed", new Object[]
208                       { pdbentry.getId() }), MessageManager.formatMessage(
209                       "label.map_sequences_to_visible_window", new Object[]
210                       { pdbentry.getId() }),
211               JOptionPane.YES_NO_CANCEL_OPTION);
212
213       if (option == JOptionPane.CANCEL_OPTION)
214       {
215         return;
216       }
217       if (option == JOptionPane.YES_OPTION)
218       {
219         // TODO : Fix multiple seq to one chain issue here.
220         ap.getStructureSelectionManager().setMapping(seq, chains,
221                 alreadyMapped, AppletFormatAdapter.FILE);
222         if (ap.getSeqPanel().seqCanvas.fr != null)
223         {
224           ap.getSeqPanel().seqCanvas.fr.featuresAdded();
225           ap.paintAlignment(true);
226         }
227
228         // Now this ChimeraViewFrame is mapped to new sequences. We must add
229         // them to the existing array
230         JInternalFrame[] frames = Desktop.instance.getAllFrames();
231
232         for (JInternalFrame frame : frames)
233         {
234           if (frame instanceof ChimeraViewFrame)
235           {
236             final ChimeraViewFrame topView = ((ChimeraViewFrame) frame);
237             // JBPNOTE: this looks like a binding routine, rather than a gui
238             // routine
239             for (int pe = 0; pe < topView.jmb.getPdbCount(); pe++)
240             {
241               if (topView.jmb.getPdbEntry(pe).getFile()
242                       .equals(
243                       alreadyMapped))
244               {
245                 topView.jmb.addSequence(pe, seq);
246                 topView.addAlignmentPanel(ap);
247                 // add it to the set used for colouring
248                 topView.useAlignmentPanelForColourbyseq(ap);
249                 topView.buildActionMenu();
250                 ap.getStructureSelectionManager()
251                         .sequenceColoursChanged(ap);
252                 break;
253               }
254             }
255           }
256         }
257
258         return;
259       }
260     }
261     // /////////////////////////////////
262     // Check if there are other Chimera views involving this alignment
263     // and prompt user about adding this molecule to one of them
264     List<ChimeraViewFrame> existingViews = getChimeraWindowsFor(ap);
265     for (ChimeraViewFrame topView : existingViews)
266     {
267       // TODO: highlight topView in view somehow
268       int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
269               MessageManager.formatMessage("label.add_pdbentry_to_view",
270                       new Object[]
271                       { pdbentry.getId(), topView.getTitle() }),
272               MessageManager
273                       .getString("label.align_to_existing_structure_view"),
274               JOptionPane.YES_NO_CANCEL_OPTION);
275       if (option == JOptionPane.CANCEL_OPTION)
276       {
277         return;
278       }
279       if (option == JOptionPane.YES_OPTION)
280       {
281         topView.useAlignmentPanelForSuperposition(ap);
282         topView.addStructure(pdbentry, seq, chains, true, ap.alignFrame);
283         return;
284       }
285     }
286     // /////////////////////////////////
287     openNewChimera(ap, new PDBEntry[]
288     { pdbentry }, new SequenceI[][]
289     { seq });
290   }
291
292   private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
293           SequenceI[][] seqs)
294   {
295     progressBar = ap.alignFrame;
296     jmb = new JalviewChimeraBindingModel(this,
297             ap.getStructureSelectionManager(), pdbentrys, seqs, null, null);
298     addAlignmentPanel(ap);
299     useAlignmentPanelForColourbyseq(ap);
300     if (pdbentrys.length > 1)
301     {
302       alignAddedStructures = true;
303       useAlignmentPanelForSuperposition(ap);
304     }
305     jmb.setColourBySequence(true);
306     setSize(400, 400); // probably should be a configurable/dynamic default here
307     initMenus();
308     worker = null;
309     {
310       addingStructures = false;
311       worker = new Thread(this);
312       worker.start();
313     }
314     this.addInternalFrameListener(new InternalFrameAdapter()
315     {
316       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
317       {
318         closeViewer();
319       }
320     });
321
322   }
323
324   /**
325    * create a new viewer containing several structures superimposed using the
326    * given alignPanel.
327    * 
328    * @param ap
329    * @param pe
330    * @param seqs
331    */
332   public ChimeraViewFrame(AlignmentPanel ap, PDBEntry[] pe,
333           SequenceI[][] seqs)
334   {
335     super();
336     openNewChimera(ap, pe, seqs);
337   }
338
339   public AlignmentPanel[] getAllAlignmentPanels()
340   {
341     AlignmentPanel[] t, list = new AlignmentPanel[0];
342     for (String setid : _aps)
343     {
344       AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
345       if (panels != null)
346       {
347         t = new AlignmentPanel[list.length + panels.length];
348         System.arraycopy(list, 0, t, 0, list.length);
349         System.arraycopy(panels, 0, t, list.length, panels.length);
350         list = t;
351       }
352     }
353
354     return list;
355   }
356
357   /**
358    * set the primary alignmentPanel reference and add another alignPanel to the
359    * list of ones to use for colouring and aligning
360    * 
361    * @param nap
362    */
363   public void addAlignmentPanel(AlignmentPanel nap)
364   {
365     if (getAlignmentPanel() == null)
366     {
367       setAlignmentPanel(nap);
368     }
369     if (!_aps.contains(nap.av.getSequenceSetId()))
370     {
371       _aps.add(nap.av.getSequenceSetId());
372     }
373   }
374
375   /**
376    * remove any references held to the given alignment panel
377    * 
378    * @param nap
379    */
380   public void removeAlignmentPanel(AlignmentPanel nap)
381   {
382     try
383     {
384       _alignwith.remove(nap);
385       _colourwith.remove(nap);
386       if (getAlignmentPanel() == nap)
387       {
388         setAlignmentPanel(null);
389         for (AlignmentPanel aps : getAllAlignmentPanels())
390         {
391           if (aps != nap)
392           {
393             setAlignmentPanel(aps);
394             break;
395           }
396         }
397       }
398     } catch (Exception ex)
399     {
400     }
401     if (getAlignmentPanel() != null)
402     {
403       buildActionMenu();
404     }
405   }
406
407   public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
408   {
409     addAlignmentPanel(nap);
410     if (!_alignwith.contains(nap))
411     {
412       _alignwith.add(nap);
413     }
414   }
415
416   public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
417   {
418     if (_alignwith.contains(nap))
419     {
420       _alignwith.remove(nap);
421     }
422   }
423
424   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap,
425           boolean enableColourBySeq)
426   {
427     useAlignmentPanelForColourbyseq(nap);
428     getBinding().setColourBySequence(enableColourBySeq);
429     seqColour.setSelected(enableColourBySeq);
430     viewerColour.setSelected(!enableColourBySeq);
431   }
432
433   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
434   {
435     addAlignmentPanel(nap);
436     if (!_colourwith.contains(nap))
437     {
438       _colourwith.add(nap);
439     }
440   }
441
442   public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
443   {
444     if (_colourwith.contains(nap))
445     {
446       _colourwith.remove(nap);
447     }
448   }
449
450   /**
451    * add a new structure (with associated sequences and chains) to this viewer,
452    * retrieving it if necessary first.
453    * 
454    * @param pdbentry
455    * @param seq
456    * @param chains
457    * @param alignFrame
458    * @param align
459    *          if true, new structure(s) will be align using associated alignment
460    */
461   private void addStructure(final PDBEntry pdbentry, final SequenceI[] seq,
462           final String[] chains, final boolean b,
463           final IProgressIndicator alignFrame)
464   {
465     if (pdbentry.getFile() == null)
466     {
467       if (worker != null && worker.isAlive())
468       {
469         // a retrieval is in progress, wait around and add ourselves to the
470         // queue.
471         new Thread(new Runnable()
472         {
473           public void run()
474           {
475             while (worker != null && worker.isAlive() && _started)
476             {
477               try
478               {
479                 Thread.sleep(100 + ((int) Math.random() * 100));
480
481               } catch (Exception e)
482               {
483               }
484
485             }
486             // and call ourselves again.
487             addStructure(pdbentry, seq, chains, b, alignFrame);
488           }
489         }).start();
490         return;
491       }
492     }
493     // otherwise, start adding the structure.
494     jmb.addSequenceAndChain(new PDBEntry[]
495     { pdbentry }, new SequenceI[][]
496     { seq }, new String[][]
497     { chains });
498     addingStructures = true;
499     _started = false;
500     alignAddedStructures = b;
501     progressBar = alignFrame; // visual indication happens on caller frame.
502     (worker = new Thread(this)).start();
503     return;
504   }
505
506   private List<ChimeraViewFrame> getChimeraWindowsFor(AlignmentPanel apanel)
507   {
508     List<ChimeraViewFrame> result = new ArrayList<ChimeraViewFrame>();
509     JInternalFrame[] frames = Desktop.instance.getAllFrames();
510
511     for (JInternalFrame frame : frames)
512     {
513       if (frame instanceof ChimeraViewFrame)
514       {
515         if (((StructureViewerBase) frame).isLinkedWith(apanel))
516         {
517           result.add((ChimeraViewFrame) frame);
518         }
519       }
520     }
521     return result;
522   }
523
524   void initChimera(String command)
525   {
526     jmb.setFinishedInit(false);
527     // TODO: consider waiting until the structure/view is fully loaded before
528     // displaying
529     jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle("Chimera", true),
530             getBounds().width, getBounds().height);
531     if (command == null)
532     {
533       command = "";
534     }
535     jmb.evalStateCommand(command, false);
536     jmb.setFinishedInit(true);
537   }
538
539   void setChainMenuItems(List<String> chainNames)
540   {
541     chainMenu.removeAll();
542     if (chainNames == null)
543     {
544       return;
545     }
546     JMenuItem menuItem = new JMenuItem(
547             MessageManager.getString("label.all"));
548     menuItem.addActionListener(new ActionListener()
549     {
550       public void actionPerformed(ActionEvent evt)
551       {
552         allChainsSelected = true;
553         for (int i = 0; i < chainMenu.getItemCount(); i++)
554         {
555           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
556           {
557             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
558           }
559         }
560         centerViewer();
561         allChainsSelected = false;
562       }
563     });
564
565     chainMenu.add(menuItem);
566
567     for (String chainName : chainNames)
568     {
569       menuItem = new JCheckBoxMenuItem(chainName, true);
570       menuItem.addItemListener(new ItemListener()
571       {
572         public void itemStateChanged(ItemEvent evt)
573         {
574           if (!allChainsSelected)
575           {
576             centerViewer();
577           }
578         }
579       });
580
581       chainMenu.add(menuItem);
582     }
583   }
584
585   void centerViewer()
586   {
587     List<String> toshow = new ArrayList<String>();
588     for (int i = 0; i < chainMenu.getItemCount(); i++)
589     {
590       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
591       {
592         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
593         if (item.isSelected())
594         {
595           toshow.add(item.getText());
596         }
597       }
598     }
599     jmb.centerViewer(toshow);
600   }
601
602   /**
603    * Close down this instance of Jalview's Chimera viewer, giving the user the
604    * option to close the associated Chimera window (process). They may wish to
605    * keep it open until they have had an opportunity to save any work.
606    */
607   public void closeViewer()
608   {
609     if (jmb.isChimeraRunning())
610     {
611       String prompt = MessageManager
612               .formatMessage("label.confirm_close_chimera", new Object[]
613               { jmb.getViewerTitle("Chimera", false) });
614       prompt = JvSwingUtils.wrapTooltip(true, prompt);
615       int confirm = JOptionPane.showConfirmDialog(this, prompt,
616               MessageManager.getString("label.close_viewer"),
617               JOptionPane.YES_NO_OPTION);
618       jmb.closeViewer(confirm == JOptionPane.YES_OPTION);
619     }
620     setAlignmentPanel(null);
621     _aps.clear();
622     _alignwith.clear();
623     _colourwith.clear();
624     // TODO: check for memory leaks where instance isn't finalised because jmb
625     // holds a reference to the window
626     jmb = null;
627   }
628
629   /**
630    * Open any newly added PDB structures in Chimera, having first fetched data
631    * from PDB (if not already saved).
632    */
633   public void run()
634   {
635     _started = true;
636     // todo - record which pdbids were successfully imported.
637     StringBuilder errormsgs = new StringBuilder(128);
638     StringBuilder files = new StringBuilder(128);
639     List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
640     List<Integer> filePDBpos = new ArrayList<Integer>();
641     PDBEntry thePdbEntry = null;
642     try
643     {
644       String[] curfiles = jmb.getPdbFile(); // files currently in viewer
645       // TODO: replace with reference fetching/transfer code (validate PDBentry
646       // as a DBRef?)
647       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
648       {
649         String file = null;
650         thePdbEntry = jmb.getPdbEntry(pi);
651         if (thePdbEntry.getFile() == null)
652         {
653           /*
654            * Retrieve PDB data, save to file, attach to PDBEntry
655            */
656           file = fetchPdbFile(thePdbEntry);
657           if (file == null)
658           {
659             errormsgs.append("'" + thePdbEntry.getId() + "' ");
660           }
661         }
662         else
663         {
664           /*
665            * Got file already - ignore if already loaded in Chimera.
666            */
667           file = new File(thePdbEntry.getFile()).getAbsoluteFile()
668                   .getPath();
669           if (curfiles != null && curfiles.length > 0)
670           {
671             addingStructures = true; // already files loaded.
672             for (int c = 0; c < curfiles.length; c++)
673             {
674               if (curfiles[c].equals(file))
675               {
676                 file = null;
677                 break;
678               }
679             }
680           }
681         }
682         if (file != null)
683         {
684           filePDB.add(thePdbEntry);
685           filePDBpos.add(Integer.valueOf(pi));
686           files.append(" \"" + Platform.escapeString(file) + "\"");
687         }
688       }
689     } catch (OutOfMemoryError oomerror)
690     {
691       new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
692               oomerror);
693     } catch (Exception ex)
694     {
695       ex.printStackTrace();
696       errormsgs.append("When retrieving pdbfiles for '"
697               + thePdbEntry.getId() + "'");
698     }
699     if (errormsgs.length() > 0)
700     {
701
702       JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
703               .formatMessage("label.pdb_entries_couldnt_be_retrieved",
704                       new Object[]
705                       { errormsgs.toString() }), MessageManager
706               .getString("label.couldnt_load_file"),
707               JOptionPane.ERROR_MESSAGE);
708     }
709
710     if (files.length() > 0)
711     {
712       if (!addingStructures)
713       {
714         try
715         {
716           initChimera("");
717         } catch (Exception ex)
718         {
719           Cache.log.error("Couldn't open Chimera viewer!", ex);
720         }
721       }
722       int num = -1;
723       for (PDBEntry pe : filePDB)
724       {
725         num++;
726         if (pe.getFile() != null)
727         {
728           try
729           {
730             int pos = filePDBpos.get(num).intValue();
731             jmb.openFile(pe);
732             jmb.addSequence(pos, jmb.getSequence()[pos]);
733             File fl = new File(pe.getFile());
734             String protocol = AppletFormatAdapter.URL;
735             try
736             {
737               if (fl.exists())
738               {
739                 protocol = AppletFormatAdapter.FILE;
740               }
741             } catch (Throwable e)
742             {
743             }
744             // Explicitly map to the filename used by Chimera ;
745             // TODO: use pe.getId() instead of pe.getFile() ?
746             jmb.getSsm().setMapping(jmb.getSequence()[pos], null,
747                     pe.getFile(),
748                     protocol);
749           } catch (OutOfMemoryError oomerror)
750           {
751             new OOMWarning(
752                     "When trying to open and map structures from Chimera!",
753                     oomerror);
754           } catch (Exception ex)
755           {
756             Cache.log.error("Couldn't open " + pe.getFile()
757                     + " in Chimera viewer!", ex);
758           } finally
759           {
760             Cache.log.debug("File locations are " + files);
761           }
762         }
763       }
764       jmb.setFinishedInit(true);
765       jmb.setLoadingFromArchive(false);
766
767       // refresh the sequence colours for the new structure(s)
768       for (AlignmentPanel ap : _colourwith)
769       {
770         jmb.updateColours(ap);
771       }
772       // do superposition if asked to
773       if (alignAddedStructures)
774       {
775         new Thread(new Runnable()
776         {
777           public void run()
778           {
779             alignStructs_withAllAlignPanels();
780           }
781         }).start();
782         alignAddedStructures = false;
783       }
784       addingStructures = false;
785     }
786     _started = false;
787     worker = null;
788   }
789
790   /**
791    * Fetch PDB data and save to a local file. Returns the full path to the file,
792    * or null if fetch fails.
793    * 
794    * @param processingEntry
795    * @return
796    * @throws Exception
797    */
798   private String fetchPdbFile(PDBEntry processingEntry) throws Exception
799   {
800     String filePath = null;
801     Pdb pdbclient = new Pdb();
802     AlignmentI pdbseq = null;
803     String pdbid = processingEntry.getId();
804     long hdl = pdbid.hashCode() - System.currentTimeMillis();
805     if (progressBar != null)
806     {
807       progressBar.setProgressBar(MessageManager.formatMessage(
808               "status.fetching_pdb", new Object[]
809               { pdbid }), hdl);
810     }
811     try
812     {
813       pdbseq = pdbclient.getSequenceRecords(pdbid);
814     } catch (OutOfMemoryError oomerror)
815     {
816       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
817     } finally
818     {
819       if (progressBar != null)
820       {
821         progressBar
822                 .setProgressBar(
823                         pdbid
824                                 + " "
825                                 + MessageManager
826                                         .getString("label.state_completed"),
827                         hdl);
828       }
829     }
830     /*
831      * If PDB data were saved and are not invalid (empty alignment), return the
832      * file path.
833      */
834     if (pdbseq != null && pdbseq.getHeight() > 0)
835     {
836       // just use the file name from the first sequence's first PDBEntry
837       filePath = new File(((PDBEntry) pdbseq.getSequenceAt(0).getPDBId()
838               .elementAt(0)).getFile()).getAbsolutePath();
839       processingEntry.setFile(filePath);
840     }
841     return filePath;
842   }
843
844   @Override
845   public void pdbFile_actionPerformed(ActionEvent actionEvent)
846   {
847     JalviewFileChooser chooser = new JalviewFileChooser(
848             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
849
850     chooser.setFileView(new JalviewFileView());
851     chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
852     chooser.setToolTipText(MessageManager.getString("action.save"));
853
854     int value = chooser.showSaveDialog(this);
855
856     if (value == JalviewFileChooser.APPROVE_OPTION)
857     {
858       BufferedReader in = null;
859       try
860       {
861         // TODO: cope with multiple PDB files in view
862         in = new BufferedReader(new FileReader(jmb.getPdbFile()[0]));
863         File outFile = chooser.getSelectedFile();
864
865         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
866         String data;
867         while ((data = in.readLine()) != null)
868         {
869           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
870           {
871             out.println(data);
872           }
873         }
874         out.close();
875       } catch (Exception ex)
876       {
877         ex.printStackTrace();
878       } finally
879       {
880         if (in != null)
881         {
882           try
883           {
884             in.close();
885           } catch (IOException e)
886           {
887             e.printStackTrace();
888           }
889         }
890       }
891     }
892   }
893
894   @Override
895   public void viewMapping_actionPerformed(ActionEvent actionEvent)
896   {
897     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
898     try
899     {
900       for (int pdbe = 0; pdbe < jmb.getPdbCount(); pdbe++)
901       {
902         cap.appendText(jmb.printMapping(jmb.getPdbEntry(pdbe).getFile()));
903         cap.appendText("\n");
904       }
905     } catch (OutOfMemoryError e)
906     {
907       new OOMWarning(
908               "composing sequence-structure alignments for display in text box.",
909               e);
910       cap.dispose();
911       return;
912     }
913     jalview.gui.Desktop.addInternalFrame(cap,
914             MessageManager.getString("label.pdb_sequence_mapping"), 550,
915             600);
916   }
917
918   @Override
919   public void eps_actionPerformed(ActionEvent e)
920   {
921     throw new Error(
922             MessageManager
923                     .getString("error.eps_generation_not_implemented"));
924   }
925
926   @Override
927   public void png_actionPerformed(ActionEvent e)
928   {
929     throw new Error(
930             MessageManager
931                     .getString("error.png_generation_not_implemented"));
932   }
933
934   @Override
935   public void viewerColour_actionPerformed(ActionEvent actionEvent)
936   {
937     if (viewerColour.isSelected())
938     {
939       // disable automatic sequence colouring.
940       jmb.setColourBySequence(false);
941     }
942   }
943
944   @Override
945   public void seqColour_actionPerformed(ActionEvent actionEvent)
946   {
947     jmb.setColourBySequence(seqColour.isSelected());
948     if (_colourwith == null)
949     {
950       _colourwith = new Vector<AlignmentPanel>();
951     }
952     if (jmb.isColourBySequence())
953     {
954       if (!jmb.isLoadingFromArchive())
955       {
956         if (_colourwith.size() == 0 && getAlignmentPanel() != null)
957         {
958           // Make the currently displayed alignment panel the associated view
959           _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
960         }
961       }
962       // Set the colour using the current view for the associated alignframe
963       for (AlignmentPanel ap : _colourwith)
964       {
965         jmb.colourBySequence(ap.av.showSequenceFeatures, ap);
966       }
967     }
968   }
969
970   @Override
971   public void chainColour_actionPerformed(ActionEvent actionEvent)
972   {
973     chainColour.setSelected(true);
974     jmb.colourByChain();
975   }
976
977   @Override
978   public void chargeColour_actionPerformed(ActionEvent actionEvent)
979   {
980     chargeColour.setSelected(true);
981     jmb.colourByCharge();
982   }
983
984   @Override
985   public void zappoColour_actionPerformed(ActionEvent actionEvent)
986   {
987     zappoColour.setSelected(true);
988     jmb.setJalviewColourScheme(new ZappoColourScheme());
989   }
990
991   @Override
992   public void taylorColour_actionPerformed(ActionEvent actionEvent)
993   {
994     taylorColour.setSelected(true);
995     jmb.setJalviewColourScheme(new TaylorColourScheme());
996   }
997
998   @Override
999   public void hydroColour_actionPerformed(ActionEvent actionEvent)
1000   {
1001     hydroColour.setSelected(true);
1002     jmb.setJalviewColourScheme(new HydrophobicColourScheme());
1003   }
1004
1005   @Override
1006   public void helixColour_actionPerformed(ActionEvent actionEvent)
1007   {
1008     helixColour.setSelected(true);
1009     jmb.setJalviewColourScheme(new HelixColourScheme());
1010   }
1011
1012   @Override
1013   public void strandColour_actionPerformed(ActionEvent actionEvent)
1014   {
1015     strandColour.setSelected(true);
1016     jmb.setJalviewColourScheme(new StrandColourScheme());
1017   }
1018
1019   @Override
1020   public void turnColour_actionPerformed(ActionEvent actionEvent)
1021   {
1022     turnColour.setSelected(true);
1023     jmb.setJalviewColourScheme(new TurnColourScheme());
1024   }
1025
1026   @Override
1027   public void buriedColour_actionPerformed(ActionEvent actionEvent)
1028   {
1029     buriedColour.setSelected(true);
1030     jmb.setJalviewColourScheme(new BuriedColourScheme());
1031   }
1032
1033   @Override
1034   public void purinePyrimidineColour_actionPerformed(ActionEvent actionEvent)
1035   {
1036     setJalviewColourScheme(new PurinePyrimidineColourScheme());
1037   }
1038
1039   @Override
1040   public void userColour_actionPerformed(ActionEvent actionEvent)
1041   {
1042     userColour.setSelected(true);
1043     new UserDefinedColours(this, null);
1044   }
1045
1046   @Override
1047   public void backGround_actionPerformed(ActionEvent actionEvent)
1048   {
1049     java.awt.Color col = JColorChooser
1050             .showDialog(this, MessageManager
1051                     .getString("label.select_backgroud_colour"), null);
1052     if (col != null)
1053     {
1054       jmb.setBackgroundColour(col);
1055     }
1056   }
1057
1058   @Override
1059   public void showHelp_actionPerformed(ActionEvent actionEvent)
1060   {
1061     try
1062     {
1063       jalview.util.BrowserLauncher
1064               .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
1065     } catch (Exception ex)
1066     {
1067     }
1068   }
1069
1070   public void updateTitleAndMenus()
1071   {
1072     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
1073     {
1074       repaint();
1075       return;
1076     }
1077     setChainMenuItems(jmb.chainNames);
1078
1079     this.setTitle(jmb.getViewerTitle("Chimera", true));
1080     if (jmb.getPdbFile().length > 1 && jmb.getSequence().length > 1)
1081     {
1082       viewerActionMenu.setVisible(true);
1083     }
1084     if (!jmb.isLoadingFromArchive())
1085     {
1086       seqColour_actionPerformed(null);
1087     }
1088   }
1089
1090   /*
1091    * (non-Javadoc)
1092    * 
1093    * @see
1094    * jalview.jbgui.GStructureViewer#alignStructs_actionPerformed(java.awt.event
1095    * .ActionEvent)
1096    */
1097   @Override
1098   protected void alignStructs_actionPerformed(ActionEvent actionEvent)
1099   {
1100     alignStructs_withAllAlignPanels();
1101   }
1102
1103   private void alignStructs_withAllAlignPanels()
1104   {
1105     if (getAlignmentPanel() == null)
1106     {
1107       return;
1108     }
1109     ;
1110     if (_alignwith.size() == 0)
1111     {
1112       _alignwith.add(getAlignmentPanel());
1113     }
1114     ;
1115     try
1116     {
1117       AlignmentI[] als = new Alignment[_alignwith.size()];
1118       ColumnSelection[] alc = new ColumnSelection[_alignwith.size()];
1119       int[] alm = new int[_alignwith.size()];
1120       int a = 0;
1121
1122       for (AlignmentPanel ap : _alignwith)
1123       {
1124         als[a] = ap.av.getAlignment();
1125         alm[a] = -1;
1126         alc[a++] = ap.av.getColumnSelection();
1127       }
1128       jmb.superposeStructures(als, alm, alc);
1129     } catch (Exception e)
1130     {
1131       StringBuffer sp = new StringBuffer();
1132       for (AlignmentPanel ap : _alignwith)
1133       {
1134         sp.append("'" + ap.alignFrame.getTitle() + "' ");
1135       }
1136       Cache.log.info("Couldn't align structures with the " + sp.toString()
1137               + "associated alignment panels.", e);
1138
1139     }
1140
1141   }
1142
1143   public void setJalviewColourScheme(ColourSchemeI ucs)
1144   {
1145     jmb.setJalviewColourScheme(ucs);
1146
1147   }
1148
1149   /**
1150    * 
1151    * @param alignment
1152    * @return first alignment panel displaying given alignment, or the default
1153    *         alignment panel
1154    */
1155   public AlignmentPanel getAlignmentPanelFor(AlignmentI alignment)
1156   {
1157     for (AlignmentPanel ap : getAllAlignmentPanels())
1158     {
1159       if (ap.av.getAlignment() == alignment)
1160       {
1161         return ap;
1162       }
1163     }
1164     return getAlignmentPanel();
1165   }
1166
1167   @Override
1168   public AAStructureBindingModel getBinding()
1169   {
1170     return jmb;
1171   }
1172
1173   /**
1174    * Ask Chimera to save its session to the designated file path. Returns true
1175    * if successful, else false.
1176    * 
1177    * @param filepath
1178    */
1179   public boolean saveSession(String filepath)
1180   {
1181     return jmb.saveSession(filepath);
1182   }
1183 }