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