JAL-2136 merged and resolved conflicts with 80edaa84d6d9beac9f0d2c71b50b7b56fd393427
[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.event.InternalFrameAdapter;
70 import javax.swing.event.InternalFrameEvent;
71 import javax.swing.event.MenuEvent;
72 import javax.swing.event.MenuListener;
73
74 /**
75  * GUI elements for handling an external chimera display
76  * 
77  * @author jprocter
78  *
79  */
80 public class ChimeraViewFrame extends StructureViewerBase
81 {
82   private JalviewChimeraBinding jmb;
83
84   private boolean allChainsSelected = false;
85
86   private IProgressIndicator progressBar = null;
87
88   /*
89    * Path to Chimera session file. This is set when an open Jalview/Chimera
90    * session is saved, or on restore from a Jalview project (if it holds the
91    * filename of any saved Chimera sessions).
92    */
93   private String chimeraSessionFile = null;
94
95   private Random random = new Random();
96
97   /**
98    * Initialise menu options.
99    */
100   private void initMenus()
101   {
102     viewerActionMenu.setText(MessageManager.getString("label.chimera"));
103     viewerColour.setText(MessageManager
104             .getString("label.colour_with_chimera"));
105     viewerColour.setToolTipText(MessageManager
106             .getString("label.let_chimera_manage_structure_colours"));
107     helpItem.setText(MessageManager.getString("label.chimera_help"));
108     seqColour.setSelected(jmb.isColourBySequence());
109     viewerColour.setSelected(!jmb.isColourBySequence());
110     if (_colourwith == null)
111     {
112       _colourwith = new Vector<AlignmentPanel>();
113     }
114     if (_alignwith == null)
115     {
116       _alignwith = new Vector<AlignmentPanel>();
117     }
118
119     // save As not yet implemented
120     savemenu.setVisible(false);
121
122     ViewSelectionMenu seqColourBy = new ViewSelectionMenu(
123             MessageManager.getString("label.colour_by"), this, _colourwith,
124             new ItemListener()
125             {
126               @Override
127               public void itemStateChanged(ItemEvent e)
128               {
129                 if (!seqColour.isSelected())
130                 {
131                   seqColour.doClick();
132                 }
133                 else
134                 {
135                   // update the Chimera display now.
136                   seqColour_actionPerformed(null);
137                 }
138               }
139             });
140     viewMenu.add(seqColourBy);
141     viewMenu.add(fitToWindow);
142
143     final ItemListener handler;
144     JMenu alpanels = new ViewSelectionMenu(
145             MessageManager.getString("label.superpose_with"), this,
146             _alignwith, handler = new ItemListener()
147             {
148               @Override
149               public void itemStateChanged(ItemEvent e)
150               {
151                 alignStructs.setEnabled(_alignwith.size() > 0);
152                 alignStructs.setToolTipText(MessageManager
153                         .formatMessage(
154                                 "label.align_structures_using_linked_alignment_views",
155                                 new Object[] { new Integer(_alignwith
156                                         .size()).toString() }));
157               }
158             });
159     handler.itemStateChanged(null);
160     viewerActionMenu.add(alpanels);
161     viewerActionMenu.addMenuListener(new MenuListener()
162     {
163
164       @Override
165       public void menuSelected(MenuEvent e)
166       {
167         handler.itemStateChanged(null);
168       }
169
170       @Override
171       public void menuDeselected(MenuEvent e)
172       {
173         // TODO Auto-generated method stub
174       }
175
176       @Override
177       public void menuCanceled(MenuEvent e)
178       {
179         // TODO Auto-generated method stub
180       }
181     });
182   }
183
184   /**
185    * add a single PDB structure to a new or existing Chimera view
186    * 
187    * @param pdbentry
188    * @param seq
189    * @param chains
190    * @param ap
191    */
192   public ChimeraViewFrame(PDBEntry pdbentry, SequenceI[] seq,
193           String[] chains, final AlignmentPanel ap)
194   {
195     this();
196     String pdbId = pdbentry.getId();
197
198     /*
199      * If the PDB file is already loaded, the user may just choose to add to an
200      * existing viewer (or cancel)
201      */
202     if (addAlreadyLoadedFile(seq, chains, ap, pdbId))
203     {
204       return;
205     }
206
207     /*
208      * Check if there are other Chimera views involving this alignment and give
209      * user the option to add and align this molecule to one of them (or cancel)
210      */
211     if (addToExistingViewer(pdbentry, seq, chains, ap, pdbId))
212     {
213       return;
214     }
215
216     /*
217      * If the options above are declined or do not apply, show the structure in
218      * a new viewer
219      */
220     openNewChimera(ap, new PDBEntry[] { pdbentry },
221             new SequenceI[][] { seq });
222   }
223
224   /**
225    * Create a helper to manage progress bar display
226    */
227   protected void createProgressBar()
228   {
229     if (progressBar == null)
230     {
231       progressBar = new ProgressBar(statusPanel, statusBar);
232     }
233   }
234
235   /**
236    * Answers true if this viewer already involves the given PDB ID
237    */
238   @Override
239   protected boolean hasPdbId(String pdbId)
240   {
241     return jmb.hasPdbId(pdbId);
242   }
243
244   private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
245           SequenceI[][] seqs)
246   {
247     createProgressBar();
248     // FIXME extractChains needs pdbentries to match IDs to PDBEntry(s) on seqs
249     jmb = new JalviewChimeraBindingModel(this,
250             ap.getStructureSelectionManager(), pdbentrys, seqs, null);
251     addAlignmentPanel(ap);
252     useAlignmentPanelForColourbyseq(ap);
253     if (pdbentrys.length > 1)
254     {
255       alignAddedStructures = true;
256       useAlignmentPanelForSuperposition(ap);
257     }
258     jmb.setColourBySequence(true);
259     setSize(400, 400); // probably should be a configurable/dynamic default here
260     initMenus();
261
262     addingStructures = false;
263     worker = new Thread(this);
264     worker.start();
265
266     this.addInternalFrameListener(new InternalFrameAdapter()
267     {
268       @Override
269       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
270       {
271         closeViewer(false);
272       }
273     });
274
275   }
276
277
278
279   /**
280    * Create a new viewer from saved session state data including Chimera session
281    * file
282    * 
283    * @param chimeraSessionFile
284    * @param alignPanel
285    * @param pdbArray
286    * @param seqsArray
287    * @param colourByChimera
288    * @param colourBySequence
289    * @param newViewId
290    */
291   public ChimeraViewFrame(String chimeraSessionFile,
292           AlignmentPanel alignPanel, PDBEntry[] pdbArray,
293           SequenceI[][] seqsArray, boolean colourByChimera,
294           boolean colourBySequence, String newViewId)
295   {
296     this();
297     setViewId(newViewId);
298     this.chimeraSessionFile = chimeraSessionFile;
299     openNewChimera(alignPanel, pdbArray, seqsArray);
300     if (colourByChimera)
301     {
302       jmb.setColourBySequence(false);
303       seqColour.setSelected(false);
304       viewerColour.setSelected(true);
305     }
306     else if (colourBySequence)
307     {
308       jmb.setColourBySequence(true);
309       seqColour.setSelected(true);
310       viewerColour.setSelected(false);
311     }
312   }
313
314   /**
315    * create a new viewer containing several structures superimposed using the
316    * given alignPanel.
317    * 
318    * @param pe
319    * @param seqs
320    * @param ap
321    */
322   public ChimeraViewFrame(PDBEntry[] pe, SequenceI[][] seqs,
323           AlignmentPanel ap)
324   {
325     this();
326     openNewChimera(ap, pe, seqs);
327   }
328
329   /**
330    * Default constructor
331    */
332   public ChimeraViewFrame()
333   {
334     super();
335
336     /*
337      * closeViewer will decide whether or not to close this frame
338      * depending on whether user chooses to Cancel or not
339      */
340     setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
341   }
342
343   /**
344    * Returns a list of any Chimera viewers in the desktop. The list is
345    * restricted to those linked to the given alignment panel if it is not null.
346    */
347   @Override
348   protected List<StructureViewerBase> getViewersFor(AlignmentPanel ap)
349   {
350     List<StructureViewerBase> result = new ArrayList<StructureViewerBase>();
351     JInternalFrame[] frames = Desktop.instance.getAllFrames();
352
353     for (JInternalFrame frame : frames)
354     {
355       if (frame instanceof ChimeraViewFrame)
356       {
357         if (ap == null || ((StructureViewerBase) frame).isLinkedWith(ap))
358         {
359           result.add((StructureViewerBase) frame);
360         }
361       }
362     }
363     return result;
364   }
365
366   /**
367    * Launch Chimera. If we have a chimera session file name, send Chimera the
368    * command to open its saved session file.
369    */
370   void initChimera()
371   {
372     jmb.setFinishedInit(false);
373     jalview.gui.Desktop.addInternalFrame(this,
374             jmb.getViewerTitle("Chimera", true), getBounds().width,
375             getBounds().height);
376
377     if (!jmb.launchChimera())
378     {
379       JvOptionPane.showMessageDialog(Desktop.desktop,
380               MessageManager.getString("label.chimera_failed"),
381               MessageManager.getString("label.error_loading_file"),
382               JvOptionPane.ERROR_MESSAGE);
383       this.dispose();
384       return;
385     }
386
387     if (this.chimeraSessionFile != null)
388     {
389       boolean opened = jmb.openSession(chimeraSessionFile);
390       if (!opened)
391       {
392         System.err
393                 .println("An error occurred opening Chimera session file "
394                         + chimeraSessionFile);
395       }
396     }
397     jmb.setFinishedInit(true);
398
399     jmb.startChimeraListener();
400   }
401
402
403   /**
404    * Show only the selected chain(s) in the viewer
405    */
406   @Override
407   void showSelectedChains()
408   {
409     List<String> toshow = new ArrayList<String>();
410     for (int i = 0; i < chainMenu.getItemCount(); i++)
411     {
412       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
413       {
414         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
415         if (item.isSelected())
416         {
417           toshow.add(item.getText());
418         }
419       }
420     }
421     jmb.showChains(toshow);
422   }
423
424   /**
425    * Close down this instance of Jalview's Chimera viewer, giving the user the
426    * option to close the associated Chimera window (process). They may wish to
427    * keep it open until they have had an opportunity to save any work.
428    * 
429    * @param closeChimera
430    *          if true, close any linked Chimera process; if false, prompt first
431    */
432   @Override
433   public void closeViewer(boolean closeChimera)
434   {
435     if (jmb != null && jmb.isChimeraRunning())
436     {
437       if (!closeChimera)
438       {
439         String prompt = MessageManager.formatMessage(
440                 "label.confirm_close_chimera",
441                 new Object[] { jmb.getViewerTitle("Chimera", false) });
442         prompt = JvSwingUtils.wrapTooltip(true, prompt);
443         int confirm = JvOptionPane.showConfirmDialog(this, prompt,
444                 MessageManager.getString("label.close_viewer"),
445                 JvOptionPane.YES_NO_CANCEL_OPTION);
446         /*
447          * abort closure if user hits escape or Cancel
448          */
449         if (confirm == JvOptionPane.CANCEL_OPTION
450                 || confirm == JvOptionPane.CLOSED_OPTION)
451         {
452           return;
453         }
454         closeChimera = confirm == JvOptionPane.YES_OPTION;
455       }
456       jmb.closeViewer(closeChimera);
457     }
458     setAlignmentPanel(null);
459     _aps.clear();
460     _alignwith.clear();
461     _colourwith.clear();
462     // TODO: check for memory leaks where instance isn't finalised because jmb
463     // holds a reference to the window
464     jmb = null;
465     dispose();
466   }
467
468   /**
469    * Open any newly added PDB structures in Chimera, having first fetched data
470    * from PDB (if not already saved).
471    */
472   @Override
473   public void run()
474   {
475     _started = true;
476     // todo - record which pdbids were successfully imported.
477     StringBuilder errormsgs = new StringBuilder(128);
478     StringBuilder files = new StringBuilder(128);
479     List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
480     List<Integer> filePDBpos = new ArrayList<Integer>();
481     PDBEntry thePdbEntry = null;
482     StructureFile pdb = null;
483     try
484     {
485       String[] curfiles = jmb.getPdbFile(); // files currently in viewer
486       // TODO: replace with reference fetching/transfer code (validate PDBentry
487       // as a DBRef?)
488       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
489       {
490         String file = null;
491         thePdbEntry = jmb.getPdbEntry(pi);
492         if (thePdbEntry.getFile() == null)
493         {
494           /*
495            * Retrieve PDB data, save to file, attach to PDBEntry
496            */
497           file = fetchPdbFile(thePdbEntry);
498           if (file == null)
499           {
500             errormsgs.append("'" + thePdbEntry.getId() + "' ");
501           }
502         }
503         else
504         {
505           /*
506            * Got file already - ignore if already loaded in Chimera.
507            */
508           file = new File(thePdbEntry.getFile()).getAbsoluteFile()
509                   .getPath();
510           if (curfiles != null && curfiles.length > 0)
511           {
512             addingStructures = true; // already files loaded.
513             for (int c = 0; c < curfiles.length; c++)
514             {
515               if (curfiles[c].equals(file))
516               {
517                 file = null;
518                 break;
519               }
520             }
521           }
522         }
523         if (file != null)
524         {
525           filePDB.add(thePdbEntry);
526           filePDBpos.add(Integer.valueOf(pi));
527           files.append(" \"" + Platform.escapeString(file) + "\"");
528         }
529       }
530     } catch (OutOfMemoryError oomerror)
531     {
532       new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
533               oomerror);
534     } catch (Exception ex)
535     {
536       ex.printStackTrace();
537       errormsgs.append("When retrieving pdbfiles for '"
538               + thePdbEntry.getId() + "'");
539     }
540     if (errormsgs.length() > 0)
541     {
542
543       JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
544               .formatMessage("label.pdb_entries_couldnt_be_retrieved",
545                       new Object[] { errormsgs.toString() }),
546               MessageManager.getString("label.couldnt_load_file"),
547               JvOptionPane.ERROR_MESSAGE);
548     }
549
550     if (files.length() > 0)
551     {
552       if (!addingStructures)
553       {
554         try
555         {
556           initChimera();
557         } catch (Exception ex)
558         {
559           Cache.log.error("Couldn't open Chimera viewer!", ex);
560         }
561       }
562       int num = -1;
563       for (PDBEntry pe : filePDB)
564       {
565         num++;
566         if (pe.getFile() != null)
567         {
568           try
569           {
570             int pos = filePDBpos.get(num).intValue();
571             long startTime = startProgressBar("Chimera "
572                     + MessageManager.getString("status.opening_file_for")
573                     + " " + pe.getId());
574             jmb.openFile(pe);
575             jmb.addSequence(pos, jmb.getSequence()[pos]);
576             File fl = new File(pe.getFile());
577             DataSourceType protocol = DataSourceType.URL;
578             try
579             {
580               if (fl.exists())
581               {
582                 protocol = DataSourceType.FILE;
583               }
584             } catch (Throwable e)
585             {
586             } finally
587             {
588               stopProgressBar("", startTime);
589             }
590             // Explicitly map to the filename used by Chimera ;
591             pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
592                     jmb.getChains()[pos], pe.getFile(), protocol,
593                     progressBar);
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. TODO: refactor to common with Jmol ? duplication
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
1148   @Override
1149   protected IProgressIndicator getIProgressIndicator()
1150   {
1151     return progressBar;
1152   }
1153 }