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