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