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