JAL-1528 bug fixes and adjustments to Chimera interface
[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(),
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   public void closeViewer()
623   {
624     jmb.closeViewer();
625     ap = null;
626     _aps.clear();
627     _alignwith.clear();
628     _colourwith.clear();
629     // TODO: check for memory leaks where instance isn't finalised because jmb
630     // holds a reference to the window
631     jmb = null;
632   }
633
634   /**
635    * Open any newly added PDB structures in Chimera, having first fetched data
636    * from PDB (if not already saved).
637    */
638   public void run()
639   {
640     _started = true;
641     // todo - record which pdbids were successfully imported.
642     StringBuilder errormsgs = new StringBuilder(128);
643     StringBuilder files = new StringBuilder(128);
644     List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
645     List<Integer> filePDBpos = new ArrayList<Integer>();
646     PDBEntry thePdbEntry = null;
647     try
648     {
649       String[] curfiles = jmb.getPdbFile(); // files currently in viewer
650       // TODO: replace with reference fetching/transfer code (validate PDBentry
651       // as a DBRef?)
652       for (int pi = 0; pi < jmb.pdbentry.length; pi++)
653       {
654         String file = null;
655         thePdbEntry = jmb.pdbentry[pi];
656         if (thePdbEntry.getFile() == null)
657         {
658           /*
659            * Retrieve PDB data, save to file, attach to PDBEntry
660            */
661           file = fetchPdbFile(thePdbEntry);
662           if (file == null)
663           {
664             errormsgs.append("'" + thePdbEntry.getId() + "' ");
665           }
666         }
667         else
668         {
669           /*
670            * Got file already - ignore if already loaded in Chimera.
671            */
672           file = new File(thePdbEntry.getFile()).getAbsoluteFile()
673                   .getPath();
674           if (curfiles != null && curfiles.length > 0)
675           {
676             addingStructures = true; // already files loaded.
677             for (int c = 0; c < curfiles.length; c++)
678             {
679               if (curfiles[c].equals(file))
680               {
681                 file = null;
682                 break;
683               }
684             }
685           }
686         }
687         if (file != null)
688         {
689           filePDB.add(thePdbEntry);
690           filePDBpos.add(Integer.valueOf(pi));
691           files.append(" \"" + Platform.escapeString(file) + "\"");
692         }
693       }
694     } catch (OutOfMemoryError oomerror)
695     {
696       new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
697               oomerror);
698     } catch (Exception ex)
699     {
700       ex.printStackTrace();
701       errormsgs.append("When retrieving pdbfiles : current was: '"
702               + thePdbEntry.getId() + "'");
703     }
704     if (errormsgs.length() > 0)
705     {
706
707       JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
708               .formatMessage("label.pdb_entries_couldnt_be_retrieved",
709                       new Object[]
710                       { errormsgs.toString() }), MessageManager
711               .getString("label.couldnt_load_file"),
712               JOptionPane.ERROR_MESSAGE);
713     }
714
715     if (files.length() > 0)
716     {
717       if (!addingStructures)
718       {
719         try
720         {
721           initChimera("");
722         } catch (Exception ex)
723         {
724           Cache.log.error("Couldn't open Chimera viewer!", ex);
725         }
726       }
727       int num = -1;
728       for (PDBEntry pe : filePDB)
729       {
730         num++;
731         if (pe.getFile() != null)
732         {
733           try
734           {
735             int pos = filePDBpos.get(num).intValue();
736             jmb.openFile(pe);
737             jmb.addSequence(pos, jmb.sequence[pos]);
738             File fl = new File(pe.getFile());
739             String protocol = AppletFormatAdapter.URL;
740             try
741             {
742               if (fl.exists())
743               {
744                 protocol = AppletFormatAdapter.FILE;
745               }
746             } catch (Throwable e)
747             {
748             }
749             // Explicitly map to the filename used by Chimera ;
750             // TODO: use pe.getId() instead of pe.getFile() ?
751             jmb.ssm.setMapping(jmb.sequence[pos], null, pe.getFile(),
752                     protocol);
753           } catch (OutOfMemoryError oomerror)
754           {
755             new OOMWarning(
756                     "When trying to open and map structures from Chimera!",
757                     oomerror);
758           } catch (Exception ex)
759           {
760             Cache.log.error("Couldn't open " + pe.getFile()
761                     + " in Chimera viewer!", ex);
762           } finally
763           {
764             Cache.log.debug("File locations are " + files);
765           }
766         }
767       }
768       jmb.setFinishedInit(true);
769       jmb.setLoadingFromArchive(false);
770
771       // refresh the sequence colours for the new structure(s)
772       for (AlignmentPanel ap : _colourwith)
773       {
774         jmb.updateColours(ap);
775       }
776       // do superposition if asked to
777       if (alignAddedStructures)
778       {
779         new Thread(new Runnable()
780         {
781           public void run()
782           {
783             alignStructs_withAllAlignPanels();
784           }
785         }).start();
786         alignAddedStructures = false;
787       }
788       addingStructures = false;
789     }
790     _started = false;
791     worker = null;
792   }
793
794   /**
795    * Fetch PDB data and save to a local file. Returns the full path to the file,
796    * or null if fetch fails.
797    * 
798    * @param processingEntry
799    * @return
800    * @throws Exception
801    */
802   private String fetchPdbFile(PDBEntry processingEntry) throws Exception
803   {
804     String filePath = null;
805     Pdb pdbclient = new Pdb();
806     AlignmentI pdbseq = null;
807     String pdbid = processingEntry.getId();
808     long hdl = pdbid.hashCode() - System.currentTimeMillis();
809     if (progressBar != null)
810     {
811       progressBar.setProgressBar(MessageManager.formatMessage(
812               "status.fetching_pdb", new Object[]
813               { pdbid }), hdl);
814     }
815     try
816     {
817       pdbseq = pdbclient.getSequenceRecords(pdbid);
818     } catch (OutOfMemoryError oomerror)
819     {
820       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
821     }
822     if (progressBar != null)
823     {
824       progressBar.setProgressBar(
825               MessageManager.getString("label.state_completed"), hdl);
826     }
827     /*
828      * If PDB data were saved and are not invalid (empty alignment), return the
829      * file path.
830      */
831     if (pdbseq != null && pdbseq.getHeight() > 0)
832     {
833       // just use the file name from the first sequence's first PDBEntry
834       filePath = new File(((PDBEntry) pdbseq.getSequenceAt(0).getPDBId()
835               .elementAt(0)).getFile()).getAbsolutePath();
836       processingEntry.setFile(filePath);
837     }
838     return filePath;
839   }
840
841   @Override
842   public void pdbFile_actionPerformed(ActionEvent actionEvent)
843   {
844     JalviewFileChooser chooser = new JalviewFileChooser(
845             jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
846
847     chooser.setFileView(new JalviewFileView());
848     chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
849     chooser.setToolTipText(MessageManager.getString("action.save"));
850
851     int value = chooser.showSaveDialog(this);
852
853     if (value == JalviewFileChooser.APPROVE_OPTION)
854     {
855       BufferedReader in = null;
856       try
857       {
858         // TODO: cope with multiple PDB files in view
859         in = new BufferedReader(new FileReader(jmb.getPdbFile()[0]));
860         File outFile = chooser.getSelectedFile();
861
862         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
863         String data;
864         while ((data = in.readLine()) != null)
865         {
866           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
867           {
868             out.println(data);
869           }
870         }
871         out.close();
872       } catch (Exception ex)
873       {
874         ex.printStackTrace();
875       } finally
876       {
877         if (in != null)
878         {
879           try
880           {
881             in.close();
882           } catch (IOException e)
883           {
884             e.printStackTrace();
885           }
886         }
887       }
888     }
889   }
890
891   @Override
892   public void viewMapping_actionPerformed(ActionEvent actionEvent)
893   {
894     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
895     try
896     {
897       for (int pdbe = 0; pdbe < jmb.pdbentry.length; pdbe++)
898       {
899         cap.appendText(jmb.printMapping(jmb.pdbentry[pdbe].getFile()));
900         cap.appendText("\n");
901       }
902     } catch (OutOfMemoryError e)
903     {
904       new OOMWarning(
905               "composing sequence-structure alignments for display in text box.",
906               e);
907       cap.dispose();
908       return;
909     }
910     jalview.gui.Desktop.addInternalFrame(cap,
911             MessageManager.getString("label.pdb_sequence_mapping"), 550,
912             600);
913   }
914
915   @Override
916   public void eps_actionPerformed(ActionEvent e)
917   {
918     throw new Error(
919             MessageManager
920                     .getString("error.eps_generation_not_implemented"));
921   }
922
923   @Override
924   public void png_actionPerformed(ActionEvent e)
925   {
926     throw new Error(
927             MessageManager
928                     .getString("error.png_generation_not_implemented"));
929   }
930
931   @Override
932   public void viewerColour_actionPerformed(ActionEvent actionEvent)
933   {
934     if (viewerColour.isSelected())
935     {
936       // disable automatic sequence colouring.
937       jmb.setColourBySequence(false);
938     }
939   }
940
941   @Override
942   public void seqColour_actionPerformed(ActionEvent actionEvent)
943   {
944     jmb.setColourBySequence(seqColour.isSelected());
945     if (_colourwith == null)
946     {
947       _colourwith = new Vector<AlignmentPanel>();
948     }
949     if (jmb.isColourBySequence())
950     {
951       if (!jmb.isLoadingFromArchive())
952       {
953         if (_colourwith.size() == 0 && ap != null)
954         {
955           // Make the currently displayed alignment panel the associated view
956           _colourwith.add(ap.alignFrame.alignPanel);
957         }
958       }
959       // Set the colour using the current view for the associated alignframe
960       for (AlignmentPanel ap : _colourwith)
961       {
962         jmb.colourBySequence(ap.av.showSequenceFeatures, ap);
963       }
964     }
965   }
966
967   @Override
968   public void chainColour_actionPerformed(ActionEvent actionEvent)
969   {
970     chainColour.setSelected(true);
971     jmb.colourByChain();
972   }
973
974   @Override
975   public void chargeColour_actionPerformed(ActionEvent actionEvent)
976   {
977     chargeColour.setSelected(true);
978     jmb.colourByCharge();
979   }
980
981   @Override
982   public void zappoColour_actionPerformed(ActionEvent actionEvent)
983   {
984     zappoColour.setSelected(true);
985     jmb.setJalviewColourScheme(new ZappoColourScheme());
986   }
987
988   @Override
989   public void taylorColour_actionPerformed(ActionEvent actionEvent)
990   {
991     taylorColour.setSelected(true);
992     jmb.setJalviewColourScheme(new TaylorColourScheme());
993   }
994
995   @Override
996   public void hydroColour_actionPerformed(ActionEvent actionEvent)
997   {
998     hydroColour.setSelected(true);
999     jmb.setJalviewColourScheme(new HydrophobicColourScheme());
1000   }
1001
1002   @Override
1003   public void helixColour_actionPerformed(ActionEvent actionEvent)
1004   {
1005     helixColour.setSelected(true);
1006     jmb.setJalviewColourScheme(new HelixColourScheme());
1007   }
1008
1009   @Override
1010   public void strandColour_actionPerformed(ActionEvent actionEvent)
1011   {
1012     strandColour.setSelected(true);
1013     jmb.setJalviewColourScheme(new StrandColourScheme());
1014   }
1015
1016   @Override
1017   public void turnColour_actionPerformed(ActionEvent actionEvent)
1018   {
1019     turnColour.setSelected(true);
1020     jmb.setJalviewColourScheme(new TurnColourScheme());
1021   }
1022
1023   @Override
1024   public void buriedColour_actionPerformed(ActionEvent actionEvent)
1025   {
1026     buriedColour.setSelected(true);
1027     jmb.setJalviewColourScheme(new BuriedColourScheme());
1028   }
1029
1030   @Override
1031   public void purinePyrimidineColour_actionPerformed(ActionEvent actionEvent)
1032   {
1033     setJalviewColourScheme(new PurinePyrimidineColourScheme());
1034   }
1035
1036   @Override
1037   public void userColour_actionPerformed(ActionEvent actionEvent)
1038   {
1039     userColour.setSelected(true);
1040     new UserDefinedColours(this, null);
1041   }
1042
1043   @Override
1044   public void backGround_actionPerformed(ActionEvent actionEvent)
1045   {
1046     java.awt.Color col = JColorChooser
1047             .showDialog(this, MessageManager
1048                     .getString("label.select_backgroud_colour"), null);
1049     if (col != null)
1050     {
1051       jmb.setBackgroundColour(col);
1052     }
1053   }
1054
1055   @Override
1056   public void showHelp_actionPerformed(ActionEvent actionEvent)
1057   {
1058     try
1059     {
1060       jalview.util.BrowserLauncher
1061               .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
1062     } catch (Exception ex)
1063     {
1064     }
1065   }
1066
1067   public String getViewId()
1068   {
1069     if (viewId == null)
1070     {
1071       viewId = System.currentTimeMillis() + "." + this.hashCode();
1072     }
1073     return viewId;
1074   }
1075
1076   public void updateTitleAndMenus()
1077   {
1078     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
1079     {
1080       repaint();
1081       return;
1082     }
1083     setChainMenuItems(jmb.chainNames);
1084
1085     this.setTitle(jmb.getViewerTitle());
1086     if (jmb.getPdbFile().length > 1 && jmb.sequence.length > 1)
1087     {
1088       viewerActionMenu.setVisible(true);
1089     }
1090     if (!jmb.isLoadingFromArchive())
1091     {
1092       seqColour_actionPerformed(null);
1093     }
1094   }
1095
1096   protected void buildChimeraActionMenu()
1097   {
1098     if (_alignwith == null)
1099     {
1100       _alignwith = new Vector<AlignmentPanel>();
1101     }
1102     if (_alignwith.size() == 0 && ap != null)
1103     {
1104       _alignwith.add(ap);
1105     }
1106     ;
1107     for (Component c : viewerActionMenu.getMenuComponents())
1108     {
1109       if (c != alignStructs)
1110       {
1111         viewerActionMenu.remove((JMenuItem) c);
1112       }
1113     }
1114   }
1115
1116   /*
1117    * (non-Javadoc)
1118    * 
1119    * @see
1120    * jalview.jbgui.GStructureViewer#alignStructs_actionPerformed(java.awt.event
1121    * .ActionEvent)
1122    */
1123   @Override
1124   protected void alignStructs_actionPerformed(ActionEvent actionEvent)
1125   {
1126     alignStructs_withAllAlignPanels();
1127   }
1128
1129   private void alignStructs_withAllAlignPanels()
1130   {
1131     if (ap == null)
1132     {
1133       return;
1134     }
1135     ;
1136     if (_alignwith.size() == 0)
1137     {
1138       _alignwith.add(ap);
1139     }
1140     ;
1141     try
1142     {
1143       AlignmentI[] als = new Alignment[_alignwith.size()];
1144       ColumnSelection[] alc = new ColumnSelection[_alignwith.size()];
1145       int[] alm = new int[_alignwith.size()];
1146       int a = 0;
1147
1148       for (AlignmentPanel ap : _alignwith)
1149       {
1150         als[a] = ap.av.getAlignment();
1151         alm[a] = -1;
1152         alc[a++] = ap.av.getColumnSelection();
1153       }
1154       jmb.superposeStructures(als, alm, alc);
1155     } catch (Exception e)
1156     {
1157       StringBuffer sp = new StringBuffer();
1158       for (AlignmentPanel ap : _alignwith)
1159       {
1160         sp.append("'" + ap.alignFrame.getTitle() + "' ");
1161       }
1162       Cache.log.info("Couldn't align structures with the " + sp.toString()
1163               + "associated alignment panels.", e);
1164
1165     }
1166
1167   }
1168
1169   public void setJalviewColourScheme(ColourSchemeI ucs)
1170   {
1171     jmb.setJalviewColourScheme(ucs);
1172
1173   }
1174
1175   /**
1176    * 
1177    * @param alignment
1178    * @return first alignment panel displaying given alignment, or the default
1179    *         alignment panel
1180    */
1181   public AlignmentPanel getAlignmentPanelFor(AlignmentI alignment)
1182   {
1183     for (AlignmentPanel ap : getAllAlignmentPanels())
1184     {
1185       if (ap.av.getAlignment() == alignment)
1186       {
1187         return ap;
1188       }
1189     }
1190     return ap;
1191   }
1192
1193   /**
1194    * 
1195    * @param ap2
1196    * @return true if this Chimera instance is linked with the given alignPanel
1197    */
1198   public boolean isLinkedWith(AlignmentPanel ap2)
1199   {
1200     return _aps.contains(ap2.av.getSequenceSetId());
1201   }
1202
1203   public boolean isUsedforaligment(AlignmentPanel ap2)
1204   {
1205
1206     return (_alignwith != null) && _alignwith.contains(ap2);
1207   }
1208
1209   public boolean isUsedforcolourby(AlignmentPanel ap2)
1210   {
1211     return (_colourwith != null) && _colourwith.contains(ap2);
1212   }
1213
1214   /**
1215    * 
1216    * @return TRUE if the view is NOT being coloured by sequence associations.
1217    */
1218   public boolean isColouredByChimera()
1219   {
1220     return !jmb.isColourBySequence();
1221   }
1222
1223   public SequenceStructureBinding getBinding()
1224   {
1225     return jmb;
1226   }
1227
1228 }