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