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