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