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