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