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