JAL-2358 ensure mappings removed on close Chimera; pull up method;
[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   /**
345    * Launch Chimera. If we have a chimera session file name, send Chimera the
346    * command to open its saved session file.
347    */
348   void initChimera()
349   {
350     jmb.setFinishedInit(false);
351     jalview.gui.Desktop.addInternalFrame(this,
352             jmb.getViewerTitle("Chimera", true), getBounds().width,
353             getBounds().height);
354
355     if (!jmb.launchChimera())
356     {
357       JvOptionPane.showMessageDialog(Desktop.desktop,
358               MessageManager.getString("label.chimera_failed"),
359               MessageManager.getString("label.error_loading_file"),
360               JvOptionPane.ERROR_MESSAGE);
361       this.dispose();
362       return;
363     }
364
365     if (this.chimeraSessionFile != null)
366     {
367       boolean opened = jmb.openSession(chimeraSessionFile);
368       if (!opened)
369       {
370         System.err
371                 .println("An error occurred opening Chimera session file "
372                         + chimeraSessionFile);
373       }
374     }
375     jmb.setFinishedInit(true);
376
377     jmb.startChimeraListener();
378   }
379
380
381   /**
382    * Show only the selected chain(s) in the viewer
383    */
384   @Override
385   void showSelectedChains()
386   {
387     List<String> toshow = new ArrayList<String>();
388     for (int i = 0; i < chainMenu.getItemCount(); i++)
389     {
390       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
391       {
392         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
393         if (item.isSelected())
394         {
395           toshow.add(item.getText());
396         }
397       }
398     }
399     jmb.showChains(toshow);
400   }
401
402   /**
403    * Close down this instance of Jalview's Chimera viewer, giving the user the
404    * option to close the associated Chimera window (process). They may wish to
405    * keep it open until they have had an opportunity to save any work.
406    * 
407    * @param forceCloseChimera
408    *          if true, close any linked Chimera process; if false, prompt first
409    */
410   @Override
411   public void closeViewer(boolean forceCloseChimera)
412   {
413     if (jmb != null)
414     {
415       if (jmb.isChimeraRunning())
416       {
417         /*
418          * force close, or prompt to close, Chimera
419          */
420         if (!forceCloseChimera)
421         {
422           String prompt = MessageManager.formatMessage(
423                   "label.confirm_close_chimera",
424                   new Object[] { jmb.getViewerTitle("Chimera", false) });
425           prompt = JvSwingUtils.wrapTooltip(true, prompt);
426           int confirm = JvOptionPane.showConfirmDialog(this, prompt,
427                   MessageManager.getString("label.close_viewer"),
428                   JvOptionPane.YES_NO_CANCEL_OPTION);
429           /*
430            * abort closure if user hits escape or Cancel
431            */
432           if (confirm == JvOptionPane.CANCEL_OPTION
433                   || confirm == JvOptionPane.CLOSED_OPTION)
434           {
435             return;
436           }
437           forceCloseChimera = confirm == JvOptionPane.YES_OPTION;
438         }
439       }
440
441       /*
442        * close the viewer plus any side-effects e.g. remove mappings
443        * note we do this also if closing Chimera triggered this method
444        */
445       jmb.closeViewer(forceCloseChimera);
446     }
447
448     setAlignmentPanel(null);
449     _aps.clear();
450     _alignwith.clear();
451     _colourwith.clear();
452     // TODO: check for memory leaks where instance isn't finalised because jmb
453     // holds a reference to the window
454     jmb = null;
455     dispose();
456   }
457
458   /**
459    * Open any newly added PDB structures in Chimera, having first fetched data
460    * from PDB (if not already saved).
461    */
462   @Override
463   public void run()
464   {
465     _started = true;
466     // todo - record which pdbids were successfully imported.
467     StringBuilder errormsgs = new StringBuilder(128);
468     StringBuilder files = new StringBuilder(128);
469     List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
470     List<Integer> filePDBpos = new ArrayList<Integer>();
471     PDBEntry thePdbEntry = null;
472     StructureFile pdb = null;
473     try
474     {
475       String[] curfiles = jmb.getPdbFile(); // files currently in viewer
476       // TODO: replace with reference fetching/transfer code (validate PDBentry
477       // as a DBRef?)
478       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
479       {
480         String file = null;
481         thePdbEntry = jmb.getPdbEntry(pi);
482         if (thePdbEntry.getFile() == null)
483         {
484           /*
485            * Retrieve PDB data, save to file, attach to PDBEntry
486            */
487           file = fetchPdbFile(thePdbEntry);
488           if (file == null)
489           {
490             errormsgs.append("'" + thePdbEntry.getId() + "' ");
491           }
492         }
493         else
494         {
495           /*
496            * Got file already - ignore if already loaded in Chimera.
497            */
498           file = new File(thePdbEntry.getFile()).getAbsoluteFile()
499                   .getPath();
500           if (curfiles != null && curfiles.length > 0)
501           {
502             addingStructures = true; // already files loaded.
503             for (int c = 0; c < curfiles.length; c++)
504             {
505               if (curfiles[c].equals(file))
506               {
507                 file = null;
508                 break;
509               }
510             }
511           }
512         }
513         if (file != null)
514         {
515           filePDB.add(thePdbEntry);
516           filePDBpos.add(Integer.valueOf(pi));
517           files.append(" \"" + Platform.escapeString(file) + "\"");
518         }
519       }
520     } catch (OutOfMemoryError oomerror)
521     {
522       new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
523               oomerror);
524     } catch (Exception ex)
525     {
526       ex.printStackTrace();
527       errormsgs.append("When retrieving pdbfiles for '"
528               + thePdbEntry.getId() + "'");
529     }
530     if (errormsgs.length() > 0)
531     {
532
533       JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
534               .formatMessage("label.pdb_entries_couldnt_be_retrieved",
535                       new Object[] { errormsgs.toString() }),
536               MessageManager.getString("label.couldnt_load_file"),
537               JvOptionPane.ERROR_MESSAGE);
538     }
539
540     if (files.length() > 0)
541     {
542       if (!addingStructures)
543       {
544         try
545         {
546           initChimera();
547         } catch (Exception ex)
548         {
549           Cache.log.error("Couldn't open Chimera viewer!", ex);
550         }
551       }
552       int num = -1;
553       for (PDBEntry pe : filePDB)
554       {
555         num++;
556         if (pe.getFile() != null)
557         {
558           try
559           {
560             int pos = filePDBpos.get(num).intValue();
561             long startTime = startProgressBar("Chimera "
562                     + MessageManager.getString("status.opening_file_for")
563                     + " " + pe.getId());
564             jmb.openFile(pe);
565             jmb.addSequence(pos, jmb.getSequence()[pos]);
566             File fl = new File(pe.getFile());
567             DataSourceType protocol = DataSourceType.URL;
568             try
569             {
570               if (fl.exists())
571               {
572                 protocol = DataSourceType.FILE;
573               }
574             } catch (Throwable e)
575             {
576             } finally
577             {
578               stopProgressBar("", startTime);
579             }
580             // Explicitly map to the filename used by Chimera ;
581             pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
582                     jmb.getChains()[pos], pe.getFile(), protocol);
583             stashFoundChains(pdb, pe.getFile());
584           } catch (OutOfMemoryError oomerror)
585           {
586             new OOMWarning(
587                     "When trying to open and map structures from Chimera!",
588                     oomerror);
589           } catch (Exception ex)
590           {
591             Cache.log.error("Couldn't open " + pe.getFile()
592                     + " in Chimera viewer!", ex);
593           } finally
594           {
595             Cache.log.debug("File locations are " + files);
596           }
597         }
598       }
599       jmb.refreshGUI();
600       jmb.setFinishedInit(true);
601       jmb.setLoadingFromArchive(false);
602
603       // refresh the sequence colours for the new structure(s)
604       for (AlignmentPanel ap : _colourwith)
605       {
606         jmb.updateColours(ap);
607       }
608       // do superposition if asked to
609       if (Cache.getDefault("AUTOSUPERIMPOSE", true) && alignAddedStructures)
610       {
611         new Thread(new Runnable()
612         {
613           @Override
614           public void run()
615           {
616             alignStructs_withAllAlignPanels();
617           }
618         }).start();
619         alignAddedStructures = false;
620       }
621       addingStructures = false;
622     }
623     _started = false;
624     worker = null;
625   }
626
627   /**
628    * Fetch PDB data and save to a local file. Returns the full path to the file,
629    * or null if fetch fails.
630    * 
631    * @param processingEntry
632    * @return
633    * @throws Exception
634    */
635
636   private void stashFoundChains(StructureFile pdb, String file)
637   {
638     for (int i = 0; i < pdb.getChains().size(); i++)
639     {
640       String chid = new String(pdb.getId() + ":"
641               + pdb.getChains().elementAt(i).id);
642       jmb.getChainNames().add(chid);
643       jmb.getChainFile().put(chid, file);
644     }
645   }
646   private String fetchPdbFile(PDBEntry processingEntry) throws Exception
647   {
648     // FIXME: this is duplicated code with Jmol frame ?
649     String filePath = null;
650     Pdb pdbclient = new Pdb();
651     AlignmentI pdbseq = null;
652     String pdbid = processingEntry.getId();
653     long handle = System.currentTimeMillis()
654             + Thread.currentThread().hashCode();
655
656     /*
657      * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
658      */
659     String msg = MessageManager.formatMessage("status.fetching_pdb",
660             new Object[] { pdbid });
661     getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
662     // long hdl = startProgressBar(MessageManager.formatMessage(
663     // "status.fetching_pdb", new Object[]
664     // { pdbid }));
665     try
666     {
667       pdbseq = pdbclient.getSequenceRecords(pdbid);
668     } catch (OutOfMemoryError oomerror)
669     {
670       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
671     } finally
672     {
673       msg = pdbid + " " + MessageManager.getString("label.state_completed");
674       getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
675       // stopProgressBar(msg, hdl);
676     }
677     /*
678      * If PDB data were saved and are not invalid (empty alignment), return the
679      * file path.
680      */
681     if (pdbseq != null && pdbseq.getHeight() > 0)
682     {
683       // just use the file name from the first sequence's first PDBEntry
684       filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
685               .elementAt(0).getFile()).getAbsolutePath();
686       processingEntry.setFile(filePath);
687     }
688     return filePath;
689   }
690
691   /**
692    * Convenience method to update the progress bar if there is one. Be sure to
693    * call stopProgressBar with the returned handle to remove the message.
694    * 
695    * @param msg
696    * @param handle
697    */
698   public long startProgressBar(String msg)
699   {
700     // TODO would rather have startProgress/stopProgress as the
701     // IProgressIndicator interface
702     long tm = random.nextLong();
703     if (progressBar != null)
704     {
705       progressBar.setProgressBar(msg, tm);
706     }
707     return tm;
708   }
709
710   /**
711    * End the progress bar with the specified handle, leaving a message (if not
712    * null) on the status bar
713    * 
714    * @param msg
715    * @param handle
716    */
717   public void stopProgressBar(String msg, long handle)
718   {
719     if (progressBar != null)
720     {
721       progressBar.setProgressBar(msg, handle);
722     }
723   }
724
725   @Override
726   public void pdbFile_actionPerformed(ActionEvent actionEvent)
727   {
728     JalviewFileChooser chooser = new JalviewFileChooser(
729             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
730
731     chooser.setFileView(new JalviewFileView());
732     chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
733     chooser.setToolTipText(MessageManager.getString("action.save"));
734
735     int value = chooser.showSaveDialog(this);
736
737     if (value == JalviewFileChooser.APPROVE_OPTION)
738     {
739       BufferedReader in = null;
740       try
741       {
742         // TODO: cope with multiple PDB files in view
743         in = new BufferedReader(new FileReader(jmb.getPdbFile()[0]));
744         File outFile = chooser.getSelectedFile();
745
746         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
747         String data;
748         while ((data = in.readLine()) != null)
749         {
750           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
751           {
752             out.println(data);
753           }
754         }
755         out.close();
756       } catch (Exception ex)
757       {
758         ex.printStackTrace();
759       } finally
760       {
761         if (in != null)
762         {
763           try
764           {
765             in.close();
766           } catch (IOException e)
767           {
768             e.printStackTrace();
769           }
770         }
771       }
772     }
773   }
774
775   @Override
776   public void viewMapping_actionPerformed(ActionEvent actionEvent)
777   {
778     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
779     try
780     {
781       cap.appendText(jmb.printMappings());
782     } catch (OutOfMemoryError e)
783     {
784       new OOMWarning(
785               "composing sequence-structure alignments for display in text box.",
786               e);
787       cap.dispose();
788       return;
789     }
790     jalview.gui.Desktop.addInternalFrame(cap,
791             MessageManager.getString("label.pdb_sequence_mapping"), 550,
792             600);
793   }
794
795   @Override
796   public void eps_actionPerformed(ActionEvent e)
797   {
798     throw new Error(
799             MessageManager
800                     .getString("error.eps_generation_not_implemented"));
801   }
802
803   @Override
804   public void png_actionPerformed(ActionEvent e)
805   {
806     throw new Error(
807             MessageManager
808                     .getString("error.png_generation_not_implemented"));
809   }
810
811   @Override
812   public void viewerColour_actionPerformed(ActionEvent actionEvent)
813   {
814     if (viewerColour.isSelected())
815     {
816       // disable automatic sequence colouring.
817       jmb.setColourBySequence(false);
818     }
819   }
820
821   @Override
822   public void seqColour_actionPerformed(ActionEvent actionEvent)
823   {
824     jmb.setColourBySequence(seqColour.isSelected());
825     if (_colourwith == null)
826     {
827       _colourwith = new Vector<AlignmentPanel>();
828     }
829     if (jmb.isColourBySequence())
830     {
831       if (!jmb.isLoadingFromArchive())
832       {
833         if (_colourwith.size() == 0 && getAlignmentPanel() != null)
834         {
835           // Make the currently displayed alignment panel the associated view
836           _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
837         }
838       }
839       // Set the colour using the current view for the associated alignframe
840       for (AlignmentPanel ap : _colourwith)
841       {
842         jmb.colourBySequence(ap.av.isShowSequenceFeatures(), ap);
843       }
844     }
845   }
846
847   @Override
848   public void chainColour_actionPerformed(ActionEvent actionEvent)
849   {
850     chainColour.setSelected(true);
851     jmb.colourByChain();
852   }
853
854   @Override
855   public void chargeColour_actionPerformed(ActionEvent actionEvent)
856   {
857     chargeColour.setSelected(true);
858     jmb.colourByCharge();
859   }
860
861   @Override
862   public void zappoColour_actionPerformed(ActionEvent actionEvent)
863   {
864     zappoColour.setSelected(true);
865     jmb.setJalviewColourScheme(new ZappoColourScheme());
866   }
867
868   @Override
869   public void taylorColour_actionPerformed(ActionEvent actionEvent)
870   {
871     taylorColour.setSelected(true);
872     jmb.setJalviewColourScheme(new TaylorColourScheme());
873   }
874
875   @Override
876   public void hydroColour_actionPerformed(ActionEvent actionEvent)
877   {
878     hydroColour.setSelected(true);
879     jmb.setJalviewColourScheme(new HydrophobicColourScheme());
880   }
881
882   @Override
883   public void helixColour_actionPerformed(ActionEvent actionEvent)
884   {
885     helixColour.setSelected(true);
886     jmb.setJalviewColourScheme(new HelixColourScheme());
887   }
888
889   @Override
890   public void strandColour_actionPerformed(ActionEvent actionEvent)
891   {
892     strandColour.setSelected(true);
893     jmb.setJalviewColourScheme(new StrandColourScheme());
894   }
895
896   @Override
897   public void turnColour_actionPerformed(ActionEvent actionEvent)
898   {
899     turnColour.setSelected(true);
900     jmb.setJalviewColourScheme(new TurnColourScheme());
901   }
902
903   @Override
904   public void buriedColour_actionPerformed(ActionEvent actionEvent)
905   {
906     buriedColour.setSelected(true);
907     jmb.setJalviewColourScheme(new BuriedColourScheme());
908   }
909
910   @Override
911   public void purinePyrimidineColour_actionPerformed(ActionEvent actionEvent)
912   {
913     setJalviewColourScheme(new PurinePyrimidineColourScheme());
914   }
915
916   @Override
917   public void userColour_actionPerformed(ActionEvent actionEvent)
918   {
919     userColour.setSelected(true);
920     new UserDefinedColours(this, null);
921   }
922
923   @Override
924   public void backGround_actionPerformed(ActionEvent actionEvent)
925   {
926     java.awt.Color col = JColorChooser
927             .showDialog(this, MessageManager
928                     .getString("label.select_backgroud_colour"), null);
929     if (col != null)
930     {
931       jmb.setBackgroundColour(col);
932     }
933   }
934
935   @Override
936   public void showHelp_actionPerformed(ActionEvent actionEvent)
937   {
938     try
939     {
940       jalview.util.BrowserLauncher
941               .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
942     } catch (Exception ex)
943     {
944     }
945   }
946
947   public void updateTitleAndMenus()
948   {
949     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
950     {
951       repaint();
952       return;
953     }
954     setChainMenuItems(jmb.getChainNames());
955
956     this.setTitle(jmb.getViewerTitle("Chimera", true));
957     if (jmb.getPdbFile().length > 1 && jmb.getSequence().length > 1)
958     {
959       viewerActionMenu.setVisible(true);
960     }
961     if (!jmb.isLoadingFromArchive())
962     {
963       seqColour_actionPerformed(null);
964     }
965   }
966
967   /*
968    * (non-Javadoc)
969    * 
970    * @see
971    * jalview.jbgui.GStructureViewer#alignStructs_actionPerformed(java.awt.event
972    * .ActionEvent)
973    */
974   @Override
975   protected void alignStructs_actionPerformed(ActionEvent actionEvent)
976   {
977     alignStructs_withAllAlignPanels();
978   }
979
980   private void alignStructs_withAllAlignPanels()
981   {
982     if (getAlignmentPanel() == null)
983     {
984       return;
985     }
986
987     if (_alignwith.size() == 0)
988     {
989       _alignwith.add(getAlignmentPanel());
990     }
991
992     try
993     {
994       AlignmentI[] als = new Alignment[_alignwith.size()];
995       ColumnSelection[] alc = new ColumnSelection[_alignwith.size()];
996       int[] alm = new int[_alignwith.size()];
997       int a = 0;
998
999       for (AlignmentPanel ap : _alignwith)
1000       {
1001         als[a] = ap.av.getAlignment();
1002         alm[a] = -1;
1003         alc[a++] = ap.av.getColumnSelection();
1004       }
1005       jmb.superposeStructures(als, alm, alc);
1006     } catch (Exception e)
1007     {
1008       StringBuffer sp = new StringBuffer();
1009       for (AlignmentPanel ap : _alignwith)
1010       {
1011         sp.append("'" + ap.alignFrame.getTitle() + "' ");
1012       }
1013       Cache.log.info("Couldn't align structures with the " + sp.toString()
1014               + "associated alignment panels.", e);
1015     }
1016   }
1017
1018   @Override
1019   public void setJalviewColourScheme(ColourSchemeI ucs)
1020   {
1021     jmb.setJalviewColourScheme(ucs);
1022
1023   }
1024
1025   /**
1026    * 
1027    * @param alignment
1028    * @return first alignment panel displaying given alignment, or the default
1029    *         alignment panel
1030    */
1031   public AlignmentPanel getAlignmentPanelFor(AlignmentI alignment)
1032   {
1033     for (AlignmentPanel ap : getAllAlignmentPanels())
1034     {
1035       if (ap.av.getAlignment() == alignment)
1036       {
1037         return ap;
1038       }
1039     }
1040     return getAlignmentPanel();
1041   }
1042
1043   @Override
1044   public AAStructureBindingModel getBinding()
1045   {
1046     return jmb;
1047   }
1048
1049   /**
1050    * Ask Chimera to save its session to the designated file path, or to a
1051    * temporary file if the path is null. Returns the file path if successful,
1052    * else null.
1053    * 
1054    * @param filepath
1055    * @see getStateInfo
1056    */
1057   protected String saveSession(String filepath)
1058   {
1059     String pathUsed = filepath;
1060     try
1061     {
1062       if (pathUsed == null)
1063       {
1064         File tempFile = File.createTempFile("chimera", ".py");
1065         tempFile.deleteOnExit();
1066         pathUsed = tempFile.getPath();
1067       }
1068       boolean result = jmb.saveSession(pathUsed);
1069       if (result)
1070       {
1071         this.chimeraSessionFile = pathUsed;
1072         return pathUsed;
1073       }
1074     } catch (IOException e)
1075     {
1076     }
1077     return null;
1078   }
1079
1080   /**
1081    * Returns a string representing the state of the Chimera session. This is
1082    * done by requesting Chimera to save its session to a temporary file, then
1083    * reading the file contents. Returns an empty string on any error.
1084    */
1085   @Override
1086   public String getStateInfo()
1087   {
1088     String sessionFile = saveSession(null);
1089     if (sessionFile == null)
1090     {
1091       return "";
1092     }
1093     InputStream is = null;
1094     try
1095     {
1096       File f = new File(sessionFile);
1097       byte[] bytes = new byte[(int) f.length()];
1098       is = new FileInputStream(sessionFile);
1099       is.read(bytes);
1100       return new String(bytes);
1101     } catch (IOException e)
1102     {
1103       return "";
1104     } finally
1105     {
1106       if (is != null)
1107       {
1108         try
1109         {
1110           is.close();
1111         } catch (IOException e)
1112         {
1113           // ignore
1114         }
1115       }
1116     }
1117   }
1118
1119   @Override
1120   protected void fitToWindow_actionPerformed()
1121   {
1122     jmb.focusView();
1123   }
1124
1125   @Override
1126   public ViewerType getViewerType()
1127   {
1128     return ViewerType.CHIMERA;
1129   }
1130
1131   @Override
1132   protected AAStructureBindingModel getBindingModel()
1133   {
1134     return jmb;
1135   }
1136 }