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