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