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