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