fdc70994f3c8a552e8012391d7d4e37ceecf86f9
[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
481   void setChainMenuItems(List<String> chainNames)
482   {
483     chainMenu.removeAll();
484     if (chainNames == null)
485     {
486       return;
487     }
488     JMenuItem menuItem = new JMenuItem(
489             MessageManager.getString("label.all"));
490     menuItem.addActionListener(new ActionListener()
491     {
492       public void actionPerformed(ActionEvent evt)
493       {
494         allChainsSelected = true;
495         for (int i = 0; i < chainMenu.getItemCount(); i++)
496         {
497           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
498           {
499             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
500           }
501         }
502         centerViewer();
503         allChainsSelected = false;
504       }
505     });
506
507     chainMenu.add(menuItem);
508
509     for (String chainName : chainNames)
510     {
511       menuItem = new JCheckBoxMenuItem(chainName, true);
512       menuItem.addItemListener(new ItemListener()
513       {
514         public void itemStateChanged(ItemEvent evt)
515         {
516           if (!allChainsSelected)
517           {
518             centerViewer();
519           }
520         }
521       });
522
523       chainMenu.add(menuItem);
524     }
525   }
526
527   void centerViewer()
528   {
529     List<String> toshow = new ArrayList<String>();
530     for (int i = 0; i < chainMenu.getItemCount(); i++)
531     {
532       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
533       {
534         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
535         if (item.isSelected())
536         {
537           toshow.add(item.getText());
538         }
539       }
540     }
541     jmb.centerViewer(toshow);
542   }
543
544   /**
545    * Close down this instance of Jalview's Chimera viewer, giving the user the
546    * option to close the associated Chimera window (process). They may wish to
547    * keep it open until they have had an opportunity to save any work.
548    * 
549    * @param closeChimera
550    *          if true, close any linked Chimera process; if false, prompt first
551    */
552   public void closeViewer(boolean closeChimera)
553   {
554     if (jmb.isChimeraRunning())
555     {
556       if (!closeChimera)
557       {
558         String prompt = MessageManager.formatMessage(
559                 "label.confirm_close_chimera", new Object[]
560                 { jmb.getViewerTitle("Chimera", false) });
561         prompt = JvSwingUtils.wrapTooltip(true, prompt);
562         int confirm = JOptionPane.showConfirmDialog(this, prompt,
563                 MessageManager.getString("label.close_viewer"),
564                 JOptionPane.YES_NO_OPTION);
565         closeChimera = confirm == JOptionPane.YES_OPTION;
566       }
567       jmb.closeViewer(closeChimera);
568     }
569     setAlignmentPanel(null);
570     _aps.clear();
571     _alignwith.clear();
572     _colourwith.clear();
573     // TODO: check for memory leaks where instance isn't finalised because jmb
574     // holds a reference to the window
575     jmb = null;
576   }
577
578   /**
579    * Open any newly added PDB structures in Chimera, having first fetched data
580    * from PDB (if not already saved).
581    */
582   public void run()
583   {
584     _started = true;
585     // todo - record which pdbids were successfully imported.
586     StringBuilder errormsgs = new StringBuilder(128);
587     StringBuilder files = new StringBuilder(128);
588     List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
589     List<Integer> filePDBpos = new ArrayList<Integer>();
590     PDBEntry thePdbEntry = null;
591     try
592     {
593       String[] curfiles = jmb.getPdbFile(); // files currently in viewer
594       // TODO: replace with reference fetching/transfer code (validate PDBentry
595       // as a DBRef?)
596       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
597       {
598         String file = null;
599         thePdbEntry = jmb.getPdbEntry(pi);
600         if (thePdbEntry.getFile() == null)
601         {
602           /*
603            * Retrieve PDB data, save to file, attach to PDBEntry
604            */
605           file = fetchPdbFile(thePdbEntry);
606           if (file == null)
607           {
608             errormsgs.append("'" + thePdbEntry.getId() + "' ");
609           }
610         }
611         else
612         {
613           /*
614            * Got file already - ignore if already loaded in Chimera.
615            */
616           file = new File(thePdbEntry.getFile()).getAbsoluteFile()
617                   .getPath();
618           if (curfiles != null && curfiles.length > 0)
619           {
620             addingStructures = true; // already files loaded.
621             for (int c = 0; c < curfiles.length; c++)
622             {
623               if (curfiles[c].equals(file))
624               {
625                 file = null;
626                 break;
627               }
628             }
629           }
630         }
631         if (file != null)
632         {
633           filePDB.add(thePdbEntry);
634           filePDBpos.add(Integer.valueOf(pi));
635           files.append(" \"" + Platform.escapeString(file) + "\"");
636         }
637       }
638     } catch (OutOfMemoryError oomerror)
639     {
640       new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
641               oomerror);
642     } catch (Exception ex)
643     {
644       ex.printStackTrace();
645       errormsgs.append("When retrieving pdbfiles for '"
646               + thePdbEntry.getId() + "'");
647     }
648     if (errormsgs.length() > 0)
649     {
650
651       JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
652               .formatMessage("label.pdb_entries_couldnt_be_retrieved",
653                       new Object[]
654                       { errormsgs.toString() }), MessageManager
655               .getString("label.couldnt_load_file"),
656               JOptionPane.ERROR_MESSAGE);
657     }
658
659     if (files.length() > 0)
660     {
661       if (!addingStructures)
662       {
663         try
664         {
665           initChimera();
666         } catch (Exception ex)
667         {
668           Cache.log.error("Couldn't open Chimera viewer!", ex);
669         }
670       }
671       int num = -1;
672       for (PDBEntry pe : filePDB)
673       {
674         num++;
675         if (pe.getFile() != null)
676         {
677           try
678           {
679             int pos = filePDBpos.get(num).intValue();
680             jmb.openFile(pe);
681             jmb.addSequence(pos, jmb.getSequence()[pos]);
682             File fl = new File(pe.getFile());
683             String protocol = AppletFormatAdapter.URL;
684             try
685             {
686               if (fl.exists())
687               {
688                 protocol = AppletFormatAdapter.FILE;
689               }
690             } catch (Throwable e)
691             {
692             }
693             // Explicitly map to the filename used by Chimera ;
694             // TODO: use pe.getId() instead of pe.getFile() ?
695             jmb.getSsm().setMapping(jmb.getSequence()[pos], null,
696                     pe.getFile(),
697                     protocol);
698           } catch (OutOfMemoryError oomerror)
699           {
700             new OOMWarning(
701                     "When trying to open and map structures from Chimera!",
702                     oomerror);
703           } catch (Exception ex)
704           {
705             Cache.log.error("Couldn't open " + pe.getFile()
706                     + " in Chimera viewer!", ex);
707           } finally
708           {
709             Cache.log.debug("File locations are " + files);
710           }
711         }
712       }
713       jmb.setFinishedInit(true);
714       jmb.setLoadingFromArchive(false);
715
716       // refresh the sequence colours for the new structure(s)
717       for (AlignmentPanel ap : _colourwith)
718       {
719         jmb.updateColours(ap);
720       }
721       // do superposition if asked to
722       if (alignAddedStructures)
723       {
724         new Thread(new Runnable()
725         {
726           public void run()
727           {
728             alignStructs_withAllAlignPanels();
729           }
730         }).start();
731         alignAddedStructures = false;
732       }
733       addingStructures = false;
734     }
735     _started = false;
736     worker = null;
737   }
738
739   /**
740    * Fetch PDB data and save to a local file. Returns the full path to the file,
741    * or null if fetch fails.
742    * 
743    * @param processingEntry
744    * @return
745    * @throws Exception
746    */
747   private String fetchPdbFile(PDBEntry processingEntry) throws Exception
748   {
749     String filePath = null;
750     Pdb pdbclient = new Pdb();
751     AlignmentI pdbseq = null;
752     String pdbid = processingEntry.getId();
753     long hdl = pdbid.hashCode() - System.currentTimeMillis();
754     if (progressBar != null)
755     {
756       progressBar.setProgressBar(MessageManager.formatMessage(
757               "status.fetching_pdb", new Object[]
758               { pdbid }), hdl);
759     }
760     try
761     {
762       pdbseq = pdbclient.getSequenceRecords(pdbid);
763     } catch (OutOfMemoryError oomerror)
764     {
765       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
766     } finally
767     {
768       if (progressBar != null)
769       {
770         progressBar
771                 .setProgressBar(
772                         pdbid
773                                 + " "
774                                 + MessageManager
775                                         .getString("label.state_completed"),
776                         hdl);
777       }
778     }
779     /*
780      * If PDB data were saved and are not invalid (empty alignment), return the
781      * file path.
782      */
783     if (pdbseq != null && pdbseq.getHeight() > 0)
784     {
785       // just use the file name from the first sequence's first PDBEntry
786       filePath = new File(((PDBEntry) pdbseq.getSequenceAt(0).getPDBId()
787               .elementAt(0)).getFile()).getAbsolutePath();
788       processingEntry.setFile(filePath);
789     }
790     return filePath;
791   }
792
793   @Override
794   public void pdbFile_actionPerformed(ActionEvent actionEvent)
795   {
796     JalviewFileChooser chooser = new JalviewFileChooser(
797             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
798
799     chooser.setFileView(new JalviewFileView());
800     chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
801     chooser.setToolTipText(MessageManager.getString("action.save"));
802
803     int value = chooser.showSaveDialog(this);
804
805     if (value == JalviewFileChooser.APPROVE_OPTION)
806     {
807       BufferedReader in = null;
808       try
809       {
810         // TODO: cope with multiple PDB files in view
811         in = new BufferedReader(new FileReader(jmb.getPdbFile()[0]));
812         File outFile = chooser.getSelectedFile();
813
814         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
815         String data;
816         while ((data = in.readLine()) != null)
817         {
818           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
819           {
820             out.println(data);
821           }
822         }
823         out.close();
824       } catch (Exception ex)
825       {
826         ex.printStackTrace();
827       } finally
828       {
829         if (in != null)
830         {
831           try
832           {
833             in.close();
834           } catch (IOException e)
835           {
836             e.printStackTrace();
837           }
838         }
839       }
840     }
841   }
842
843   @Override
844   public void viewMapping_actionPerformed(ActionEvent actionEvent)
845   {
846     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
847     try
848     {
849       for (int pdbe = 0; pdbe < jmb.getPdbCount(); pdbe++)
850       {
851         cap.appendText(jmb.printMapping(jmb.getPdbEntry(pdbe).getFile()));
852         cap.appendText("\n");
853       }
854     } catch (OutOfMemoryError e)
855     {
856       new OOMWarning(
857               "composing sequence-structure alignments for display in text box.",
858               e);
859       cap.dispose();
860       return;
861     }
862     jalview.gui.Desktop.addInternalFrame(cap,
863             MessageManager.getString("label.pdb_sequence_mapping"), 550,
864             600);
865   }
866
867   @Override
868   public void eps_actionPerformed(ActionEvent e)
869   {
870     throw new Error(
871             MessageManager
872                     .getString("error.eps_generation_not_implemented"));
873   }
874
875   @Override
876   public void png_actionPerformed(ActionEvent e)
877   {
878     throw new Error(
879             MessageManager
880                     .getString("error.png_generation_not_implemented"));
881   }
882
883   @Override
884   public void viewerColour_actionPerformed(ActionEvent actionEvent)
885   {
886     if (viewerColour.isSelected())
887     {
888       // disable automatic sequence colouring.
889       jmb.setColourBySequence(false);
890     }
891   }
892
893   @Override
894   public void seqColour_actionPerformed(ActionEvent actionEvent)
895   {
896     jmb.setColourBySequence(seqColour.isSelected());
897     if (_colourwith == null)
898     {
899       _colourwith = new Vector<AlignmentPanel>();
900     }
901     if (jmb.isColourBySequence())
902     {
903       if (!jmb.isLoadingFromArchive())
904       {
905         if (_colourwith.size() == 0 && getAlignmentPanel() != null)
906         {
907           // Make the currently displayed alignment panel the associated view
908           _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
909         }
910       }
911       // Set the colour using the current view for the associated alignframe
912       for (AlignmentPanel ap : _colourwith)
913       {
914         jmb.colourBySequence(ap.av.isShowSequenceFeatures(), ap);
915       }
916     }
917   }
918
919   @Override
920   public void chainColour_actionPerformed(ActionEvent actionEvent)
921   {
922     chainColour.setSelected(true);
923     jmb.colourByChain();
924   }
925
926   @Override
927   public void chargeColour_actionPerformed(ActionEvent actionEvent)
928   {
929     chargeColour.setSelected(true);
930     jmb.colourByCharge();
931   }
932
933   @Override
934   public void zappoColour_actionPerformed(ActionEvent actionEvent)
935   {
936     zappoColour.setSelected(true);
937     jmb.setJalviewColourScheme(new ZappoColourScheme());
938   }
939
940   @Override
941   public void taylorColour_actionPerformed(ActionEvent actionEvent)
942   {
943     taylorColour.setSelected(true);
944     jmb.setJalviewColourScheme(new TaylorColourScheme());
945   }
946
947   @Override
948   public void hydroColour_actionPerformed(ActionEvent actionEvent)
949   {
950     hydroColour.setSelected(true);
951     jmb.setJalviewColourScheme(new HydrophobicColourScheme());
952   }
953
954   @Override
955   public void helixColour_actionPerformed(ActionEvent actionEvent)
956   {
957     helixColour.setSelected(true);
958     jmb.setJalviewColourScheme(new HelixColourScheme());
959   }
960
961   @Override
962   public void strandColour_actionPerformed(ActionEvent actionEvent)
963   {
964     strandColour.setSelected(true);
965     jmb.setJalviewColourScheme(new StrandColourScheme());
966   }
967
968   @Override
969   public void turnColour_actionPerformed(ActionEvent actionEvent)
970   {
971     turnColour.setSelected(true);
972     jmb.setJalviewColourScheme(new TurnColourScheme());
973   }
974
975   @Override
976   public void buriedColour_actionPerformed(ActionEvent actionEvent)
977   {
978     buriedColour.setSelected(true);
979     jmb.setJalviewColourScheme(new BuriedColourScheme());
980   }
981
982   @Override
983   public void purinePyrimidineColour_actionPerformed(ActionEvent actionEvent)
984   {
985     setJalviewColourScheme(new PurinePyrimidineColourScheme());
986   }
987
988   @Override
989   public void userColour_actionPerformed(ActionEvent actionEvent)
990   {
991     userColour.setSelected(true);
992     new UserDefinedColours(this, null);
993   }
994
995   @Override
996   public void backGround_actionPerformed(ActionEvent actionEvent)
997   {
998     java.awt.Color col = JColorChooser
999             .showDialog(this, MessageManager
1000                     .getString("label.select_backgroud_colour"), null);
1001     if (col != null)
1002     {
1003       jmb.setBackgroundColour(col);
1004     }
1005   }
1006
1007   @Override
1008   public void showHelp_actionPerformed(ActionEvent actionEvent)
1009   {
1010     try
1011     {
1012       jalview.util.BrowserLauncher
1013               .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
1014     } catch (Exception ex)
1015     {
1016     }
1017   }
1018
1019   public void updateTitleAndMenus()
1020   {
1021     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
1022     {
1023       repaint();
1024       return;
1025     }
1026     setChainMenuItems(jmb.getChainNames());
1027
1028     this.setTitle(jmb.getViewerTitle("Chimera", true));
1029     if (jmb.getPdbFile().length > 1 && jmb.getSequence().length > 1)
1030     {
1031       viewerActionMenu.setVisible(true);
1032     }
1033     if (!jmb.isLoadingFromArchive())
1034     {
1035       seqColour_actionPerformed(null);
1036     }
1037   }
1038
1039   /*
1040    * (non-Javadoc)
1041    * 
1042    * @see
1043    * jalview.jbgui.GStructureViewer#alignStructs_actionPerformed(java.awt.event
1044    * .ActionEvent)
1045    */
1046   @Override
1047   protected void alignStructs_actionPerformed(ActionEvent actionEvent)
1048   {
1049     alignStructs_withAllAlignPanels();
1050   }
1051
1052   private void alignStructs_withAllAlignPanels()
1053   {
1054     if (getAlignmentPanel() == null)
1055     {
1056       return;
1057     }
1058     ;
1059     if (_alignwith.size() == 0)
1060     {
1061       _alignwith.add(getAlignmentPanel());
1062     }
1063     ;
1064     try
1065     {
1066       AlignmentI[] als = new Alignment[_alignwith.size()];
1067       ColumnSelection[] alc = new ColumnSelection[_alignwith.size()];
1068       int[] alm = new int[_alignwith.size()];
1069       int a = 0;
1070
1071       for (AlignmentPanel ap : _alignwith)
1072       {
1073         als[a] = ap.av.getAlignment();
1074         alm[a] = -1;
1075         alc[a++] = ap.av.getColumnSelection();
1076       }
1077       jmb.superposeStructures(als, alm, alc);
1078     } catch (Exception e)
1079     {
1080       StringBuffer sp = new StringBuffer();
1081       for (AlignmentPanel ap : _alignwith)
1082       {
1083         sp.append("'" + ap.alignFrame.getTitle() + "' ");
1084       }
1085       Cache.log.info("Couldn't align structures with the " + sp.toString()
1086               + "associated alignment panels.", e);
1087
1088     }
1089
1090   }
1091
1092   public void setJalviewColourScheme(ColourSchemeI ucs)
1093   {
1094     jmb.setJalviewColourScheme(ucs);
1095
1096   }
1097
1098   /**
1099    * 
1100    * @param alignment
1101    * @return first alignment panel displaying given alignment, or the default
1102    *         alignment panel
1103    */
1104   public AlignmentPanel getAlignmentPanelFor(AlignmentI alignment)
1105   {
1106     for (AlignmentPanel ap : getAllAlignmentPanels())
1107     {
1108       if (ap.av.getAlignment() == alignment)
1109       {
1110         return ap;
1111       }
1112     }
1113     return getAlignmentPanel();
1114   }
1115
1116   @Override
1117   public AAStructureBindingModel getBinding()
1118   {
1119     return jmb;
1120   }
1121
1122   /**
1123    * Ask Chimera to save its session to the designated file path. Returns true
1124    * if successful, else false.
1125    * 
1126    * @param filepath
1127    * @see getStateInfo
1128    */
1129   public boolean saveSession(String filepath)
1130   {
1131     boolean result = jmb.saveSession(filepath);
1132     if (result)
1133     {
1134       this.chimeraSessionFile = filepath;
1135     }
1136     return result;
1137   }
1138
1139   /**
1140    * Returns the file path of the Chimera session file the last time it was
1141    * saved. If it was never saved, returns an empty string. There is no
1142    * guarantee that the Chimera session has not changed since it was saved.
1143    */
1144   @Override
1145   public String getStateInfo()
1146   {
1147     return this.chimeraSessionFile;
1148   }
1149 }