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