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