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