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