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