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