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