eadc2ade2091125258f05c67fb47a08a2602f891
[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.api.FeatureRenderer;
24 import jalview.bin.Cache;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.PDBEntry;
27 import jalview.datamodel.SequenceI;
28 import jalview.ext.rbvi.chimera.ChimeraCommands;
29 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
30 import jalview.gui.StructureViewer.ViewerType;
31 import jalview.io.DataSourceType;
32 import jalview.io.StructureFile;
33 import jalview.structures.models.AAStructureBindingModel;
34 import jalview.util.BrowserLauncher;
35 import jalview.util.MessageManager;
36 import jalview.util.Platform;
37 import jalview.ws.dbsources.Pdb;
38
39 import java.awt.event.ActionEvent;
40 import java.awt.event.ActionListener;
41 import java.awt.event.MouseAdapter;
42 import java.awt.event.MouseEvent;
43 import java.io.File;
44 import java.io.FileInputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.List;
50 import java.util.Random;
51
52 import javax.swing.JCheckBoxMenuItem;
53 import javax.swing.JInternalFrame;
54 import javax.swing.JMenu;
55 import javax.swing.JMenuItem;
56 import javax.swing.event.InternalFrameAdapter;
57 import javax.swing.event.InternalFrameEvent;
58
59 /**
60  * GUI elements for handling an external chimera display
61  * 
62  * @author jprocter
63  *
64  */
65 public class ChimeraViewFrame extends StructureViewerBase
66 {
67   private JalviewChimeraBinding jmb;
68
69   private IProgressIndicator progressBar = null;
70
71   /*
72    * Path to Chimera session file. This is set when an open Jalview/Chimera
73    * session is saved, or on restore from a Jalview project (if it holds the
74    * filename of any saved Chimera sessions).
75    */
76   private String chimeraSessionFile = null;
77
78   private Random random = new Random();
79
80   private int myWidth = 500;
81
82   private int myHeight = 150;
83
84   /**
85    * Initialise menu options.
86    */
87   @Override
88   protected void initMenus()
89   {
90     super.initMenus();
91
92     viewerActionMenu.setText(MessageManager.getString("label.chimera"));
93
94     viewerColour.setText(MessageManager
95             .getString("label.colour_with_chimera"));
96     viewerColour.setToolTipText(MessageManager
97             .getString("label.let_chimera_manage_structure_colours"));
98
99     helpItem.setText(MessageManager.getString("label.chimera_help"));
100     savemenu.setVisible(false); // not yet implemented
101     viewMenu.add(fitToWindow);
102
103     /*
104      * exchange of Jalview features and Chimera attributes is for now
105      * an optionally enabled experimental feature
106      */
107     if (Desktop.instance.showExperimental())
108     {
109       JMenuItem writeFeatures = new JMenuItem(
110               MessageManager.getString("label.create_chimera_attributes"));
111       writeFeatures.setToolTipText(MessageManager
112               .getString("label.create_chimera_attributes_tip"));
113       writeFeatures.addActionListener(new ActionListener()
114       {
115         @Override
116         public void actionPerformed(ActionEvent e)
117         {
118           sendFeaturesToChimera();
119         }
120       });
121       viewerActionMenu.add(writeFeatures);
122
123       final JMenu fetchAttributes = new JMenu(
124               MessageManager.getString("label.fetch_chimera_attributes"));
125       fetchAttributes.setToolTipText(MessageManager
126               .getString("label.fetch_chimera_attributes_tip"));
127       fetchAttributes.addMouseListener(new MouseAdapter()
128       {
129
130         @Override
131         public void mouseEntered(MouseEvent e)
132         {
133           buildAttributesMenu(fetchAttributes);
134         }
135       });
136       viewerActionMenu.add(fetchAttributes);
137     }
138   }
139
140   /**
141    * Query Chimera for its residue attribute names and add them as items off the
142    * attributes menu
143    * 
144    * @param attributesMenu
145    */
146   protected void buildAttributesMenu(JMenu attributesMenu)
147   {
148     List<String> atts = jmb.sendChimeraCommand("list resattr", true);
149     if (atts == null)
150     {
151       return;
152     }
153     attributesMenu.removeAll();
154     Collections.sort(atts);
155     for (String att : atts)
156     {
157       final String attName = att.split(" ")[1];
158
159       /*
160        * ignore 'jv_*' attributes, as these are Jalview features that have
161        * been transferred to residue attributes in Chimera!
162        */
163       if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX))
164       {
165         JMenuItem menuItem = new JMenuItem(attName);
166         menuItem.addActionListener(new ActionListener()
167         {
168           @Override
169           public void actionPerformed(ActionEvent e)
170           {
171             getChimeraAttributes(attName);
172           }
173         });
174         attributesMenu.add(menuItem);
175       }
176     }
177   }
178
179   /**
180    * Read residues in Chimera with the given attribute name, and set as features
181    * on the corresponding sequence positions (if any)
182    * 
183    * @param attName
184    */
185   protected void getChimeraAttributes(String attName)
186   {
187     jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel());
188   }
189
190   /**
191    * Send a command to Chimera to create residue attributes for Jalview features
192    * <p>
193    * The syntax is: setattr r <attName> <attValue> <atomSpec>
194    * <p>
195    * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A
196    */
197   protected void sendFeaturesToChimera()
198   {
199     int count = jmb.sendFeaturesToViewer(getAlignmentPanel());
200     statusBar.setText(MessageManager.formatMessage("label.attributes_set",
201             count));
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   private void openNewChimera(AlignmentPanel ap, PDBEntry[] pdbentrys,
256           SequenceI[][] seqs)
257   {
258     createProgressBar();
259     jmb = new JalviewChimeraBindingModel(this,
260             ap.getStructureSelectionManager(), pdbentrys, seqs, null);
261     addAlignmentPanel(ap);
262     useAlignmentPanelForColourbyseq(ap);
263
264     if (pdbentrys.length > 1)
265     {
266       alignAddedStructures = true;
267       useAlignmentPanelForSuperposition(ap);
268     }
269     jmb.setColourBySequence(true);
270     setSize(myWidth, myHeight);
271     initMenus();
272
273     addingStructures = false;
274     worker = new Thread(this);
275     worker.start();
276
277     this.addInternalFrameListener(new InternalFrameAdapter()
278     {
279       @Override
280       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
281       {
282         closeViewer(false);
283       }
284     });
285
286   }
287
288   /**
289    * Create a new viewer from saved session state data including Chimera session
290    * file
291    * 
292    * @param chimeraSessionFile
293    * @param alignPanel
294    * @param pdbArray
295    * @param seqsArray
296    * @param colourByChimera
297    * @param colourBySequence
298    * @param newViewId
299    */
300   public ChimeraViewFrame(String chimeraSessionFile,
301           AlignmentPanel alignPanel, PDBEntry[] pdbArray,
302           SequenceI[][] seqsArray, boolean colourByChimera,
303           boolean colourBySequence, String newViewId)
304   {
305     this();
306     setViewId(newViewId);
307     this.chimeraSessionFile = chimeraSessionFile;
308     openNewChimera(alignPanel, pdbArray, seqsArray);
309     if (colourByChimera)
310     {
311       jmb.setColourBySequence(false);
312       seqColour.setSelected(false);
313       viewerColour.setSelected(true);
314     }
315     else if (colourBySequence)
316     {
317       jmb.setColourBySequence(true);
318       seqColour.setSelected(true);
319       viewerColour.setSelected(false);
320     }
321   }
322
323   /**
324    * create a new viewer containing several structures superimposed using the
325    * given alignPanel.
326    * 
327    * @param pe
328    * @param seqs
329    * @param ap
330    */
331   public ChimeraViewFrame(PDBEntry[] pe, SequenceI[][] seqs,
332           AlignmentPanel ap)
333   {
334     this();
335     openNewChimera(ap, pe, seqs);
336   }
337
338   /**
339    * Default constructor
340    */
341   public ChimeraViewFrame()
342   {
343     super();
344
345     /*
346      * closeViewer will decide whether or not to close this frame
347      * depending on whether user chooses to Cancel or not
348      */
349     setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE);
350   }
351
352   /**
353    * Returns a list of any Chimera viewers in the desktop. The list is
354    * restricted to those linked to the given alignment panel if it is not null.
355    */
356   @Override
357   protected List<StructureViewerBase> getViewersFor(AlignmentPanel ap)
358   {
359     List<StructureViewerBase> result = new ArrayList<StructureViewerBase>();
360     JInternalFrame[] frames = Desktop.instance.getAllFrames();
361
362     for (JInternalFrame frame : frames)
363     {
364       if (frame instanceof ChimeraViewFrame)
365       {
366         if (ap == null || ((StructureViewerBase) frame).isLinkedWith(ap))
367         {
368           result.add((StructureViewerBase) frame);
369         }
370       }
371     }
372     return result;
373   }
374
375   /**
376    * Launch Chimera. If we have a chimera session file name, send Chimera the
377    * command to open its saved session file.
378    */
379   void initChimera()
380   {
381     jmb.setFinishedInit(false);
382     Desktop.addInternalFrame(this,
383             jmb.getViewerTitle(getViewerName(), true), getBounds().width,
384             getBounds().height);
385
386     if (!jmb.launchChimera())
387     {
388       JvOptionPane.showMessageDialog(Desktop.desktop,
389               MessageManager.getString("label.chimera_failed"),
390               MessageManager.getString("label.error_loading_file"),
391               JvOptionPane.ERROR_MESSAGE);
392       this.dispose();
393       return;
394     }
395
396     if (this.chimeraSessionFile != null)
397     {
398       boolean opened = jmb.openSession(chimeraSessionFile);
399       if (!opened)
400       {
401         System.err
402                 .println("An error occurred opening Chimera session file "
403                         + chimeraSessionFile);
404       }
405     }
406
407     jmb.startChimeraListener();
408   }
409
410   /**
411    * Show only the selected chain(s) in the viewer
412    */
413   @Override
414   void showSelectedChains()
415   {
416     List<String> toshow = new ArrayList<String>();
417     for (int i = 0; i < chainMenu.getItemCount(); i++)
418     {
419       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
420       {
421         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
422         if (item.isSelected())
423         {
424           toshow.add(item.getText());
425         }
426       }
427     }
428     jmb.showChains(toshow);
429   }
430
431   /**
432    * Close down this instance of Jalview's Chimera viewer, giving the user the
433    * option to close the associated Chimera window (process). They may wish to
434    * keep it open until they have had an opportunity to save any work.
435    * 
436    * @param closeChimera
437    *          if true, close any linked Chimera process; if false, prompt first
438    */
439   @Override
440   public void closeViewer(boolean closeChimera)
441   {
442     if (jmb != null && jmb.isChimeraRunning())
443     {
444       if (!closeChimera)
445       {
446         String prompt = MessageManager.formatMessage(
447                 "label.confirm_close_chimera",
448                         new Object[] { jmb.getViewerTitle(getViewerName(),
449                                 false) });
450         prompt = JvSwingUtils.wrapTooltip(true, prompt);
451         int confirm = JvOptionPane.showConfirmDialog(this, prompt,
452                 MessageManager.getString("label.close_viewer"),
453                 JvOptionPane.YES_NO_CANCEL_OPTION);
454         /*
455          * abort closure if user hits escape or Cancel
456          */
457         if (confirm == JvOptionPane.CANCEL_OPTION
458                 || confirm == JvOptionPane.CLOSED_OPTION)
459         {
460           return;
461         }
462         closeChimera = confirm == JvOptionPane.YES_OPTION;
463       }
464       jmb.closeViewer(closeChimera);
465     }
466     setAlignmentPanel(null);
467     _aps.clear();
468     _alignwith.clear();
469     _colourwith.clear();
470     // TODO: check for memory leaks where instance isn't finalised because jmb
471     // holds a reference to the window
472     jmb = null;
473     dispose();
474   }
475
476   /**
477    * Open any newly added PDB structures in Chimera, having first fetched data
478    * from PDB (if not already saved).
479    */
480   @Override
481   public void run()
482   {
483     _started = true;
484     // todo - record which pdbids were successfully imported.
485     StringBuilder errormsgs = new StringBuilder(128);
486     StringBuilder files = new StringBuilder(128);
487     List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
488     List<Integer> filePDBpos = new ArrayList<Integer>();
489     PDBEntry thePdbEntry = null;
490     StructureFile pdb = null;
491     try
492     {
493       String[] curfiles = jmb.getStructureFiles(); // files currently in viewer
494       // TODO: replace with reference fetching/transfer code (validate PDBentry
495       // as a DBRef?)
496       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
497       {
498         String file = null;
499         thePdbEntry = jmb.getPdbEntry(pi);
500         if (thePdbEntry.getFile() == null)
501         {
502           /*
503            * Retrieve PDB data, save to file, attach to PDBEntry
504            */
505           file = fetchPdbFile(thePdbEntry);
506           if (file == null)
507           {
508             errormsgs.append("'" + thePdbEntry.getId() + "' ");
509           }
510         }
511         else
512         {
513           /*
514            * Got file already - ignore if already loaded in Chimera.
515            */
516           file = new File(thePdbEntry.getFile()).getAbsoluteFile()
517                   .getPath();
518           if (curfiles != null && curfiles.length > 0)
519           {
520             addingStructures = true; // already files loaded.
521             for (int c = 0; c < curfiles.length; c++)
522             {
523               if (curfiles[c].equals(file))
524               {
525                 file = null;
526                 break;
527               }
528             }
529           }
530         }
531         if (file != null)
532         {
533           filePDB.add(thePdbEntry);
534           filePDBpos.add(Integer.valueOf(pi));
535           files.append(" \"" + Platform.escapeString(file) + "\"");
536         }
537       }
538     } catch (OutOfMemoryError oomerror)
539     {
540       new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
541               oomerror);
542     } catch (Exception ex)
543     {
544       ex.printStackTrace();
545       errormsgs.append("When retrieving pdbfiles for '"
546               + thePdbEntry.getId() + "'");
547     }
548     if (errormsgs.length() > 0)
549     {
550
551       JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
552               .formatMessage("label.pdb_entries_couldnt_be_retrieved",
553                       new Object[] { errormsgs.toString() }),
554               MessageManager.getString("label.couldnt_load_file"),
555               JvOptionPane.ERROR_MESSAGE);
556     }
557
558     if (files.length() > 0)
559     {
560       jmb.setFinishedInit(false);
561       if (!addingStructures)
562       {
563         try
564         {
565           initChimera();
566         } catch (Exception ex)
567         {
568           Cache.log.error("Couldn't open Chimera viewer!", ex);
569         }
570       }
571       int num = -1;
572       for (PDBEntry pe : filePDB)
573       {
574         num++;
575         if (pe.getFile() != null)
576         {
577           try
578           {
579             int pos = filePDBpos.get(num).intValue();
580             long startTime = startProgressBar(getViewerName() + " "
581                     + MessageManager.getString("status.opening_file_for")
582                     + " " + pe.getId());
583             jmb.openFile(pe);
584             jmb.addSequence(pos, jmb.getSequence()[pos]);
585             File fl = new File(pe.getFile());
586             DataSourceType protocol = DataSourceType.URL;
587             try
588             {
589               if (fl.exists())
590               {
591                 protocol = DataSourceType.FILE;
592               }
593             } catch (Throwable e)
594             {
595             } finally
596             {
597               stopProgressBar("", startTime);
598             }
599             // Explicitly map to the filename used by Chimera ;
600             pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
601                     jmb.getChains()[pos], pe.getFile(), protocol);
602             stashFoundChains(pdb, pe.getFile());
603           } catch (OutOfMemoryError oomerror)
604           {
605             new OOMWarning(
606                     "When trying to open and map structures from Chimera!",
607                     oomerror);
608           } catch (Exception ex)
609           {
610             Cache.log.error("Couldn't open " + pe.getFile()
611                     + " in Chimera viewer!", ex);
612           } finally
613           {
614             Cache.log.debug("File locations are " + files);
615           }
616         }
617       }
618
619       jmb.refreshGUI();
620       jmb.setFinishedInit(true);
621       jmb.setLoadingFromArchive(false);
622
623       /*
624        * ensure that any newly discovered features (e.g. RESNUM)
625        * are added to any open feature settings dialog
626        */
627       FeatureRenderer fr = getBinding().getFeatureRenderer(null);
628       if (fr != null)
629       {
630         fr.featuresAdded();
631       }
632
633       // refresh the sequence colours for the new structure(s)
634       for (AlignmentPanel ap : _colourwith)
635       {
636         jmb.updateColours(ap);
637       }
638       // do superposition if asked to
639       if (Cache.getDefault("AUTOSUPERIMPOSE", true) && alignAddedStructures)
640       {
641         new Thread(new Runnable()
642         {
643           @Override
644           public void run()
645           {
646             alignStructs_withAllAlignPanels();
647           }
648         }).start();
649         alignAddedStructures = false;
650       }
651       addingStructures = false;
652     }
653     _started = false;
654     worker = null;
655   }
656
657   /**
658    * Fetch PDB data and save to a local file. Returns the full path to the file,
659    * or null if fetch fails.
660    * 
661    * @param processingEntry
662    * @return
663    * @throws Exception
664    */
665
666   private void stashFoundChains(StructureFile pdb, String file)
667   {
668     for (int i = 0; i < pdb.getChains().size(); i++)
669     {
670       String chid = new String(pdb.getId() + ":"
671               + pdb.getChains().elementAt(i).id);
672       jmb.getChainNames().add(chid);
673       jmb.getChainFile().put(chid, file);
674     }
675   }
676   private String fetchPdbFile(PDBEntry processingEntry) throws Exception
677   {
678     // FIXME: this is duplicated code with Jmol frame ?
679     String filePath = null;
680     Pdb pdbclient = new Pdb();
681     AlignmentI pdbseq = null;
682     String pdbid = processingEntry.getId();
683     long handle = System.currentTimeMillis()
684             + Thread.currentThread().hashCode();
685
686     /*
687      * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
688      */
689     String msg = MessageManager.formatMessage("status.fetching_pdb",
690             new Object[] { pdbid });
691     getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
692     // long hdl = startProgressBar(MessageManager.formatMessage(
693     // "status.fetching_pdb", new Object[]
694     // { pdbid }));
695     try
696     {
697       pdbseq = pdbclient.getSequenceRecords(pdbid);
698     } catch (OutOfMemoryError oomerror)
699     {
700       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
701     } finally
702     {
703       msg = pdbid + " " + MessageManager.getString("label.state_completed");
704       getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
705       // stopProgressBar(msg, hdl);
706     }
707     /*
708      * If PDB data were saved and are not invalid (empty alignment), return the
709      * file path.
710      */
711     if (pdbseq != null && pdbseq.getHeight() > 0)
712     {
713       // just use the file name from the first sequence's first PDBEntry
714       filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
715               .elementAt(0).getFile()).getAbsolutePath();
716       processingEntry.setFile(filePath);
717     }
718     return filePath;
719   }
720
721   /**
722    * Convenience method to update the progress bar if there is one. Be sure to
723    * call stopProgressBar with the returned handle to remove the message.
724    * 
725    * @param msg
726    * @param handle
727    */
728   public long startProgressBar(String msg)
729   {
730     // TODO would rather have startProgress/stopProgress as the
731     // IProgressIndicator interface
732     long tm = random.nextLong();
733     if (progressBar != null)
734     {
735       progressBar.setProgressBar(msg, tm);
736     }
737     return tm;
738   }
739
740   /**
741    * End the progress bar with the specified handle, leaving a message (if not
742    * null) on the status bar
743    * 
744    * @param msg
745    * @param handle
746    */
747   public void stopProgressBar(String msg, long handle)
748   {
749     if (progressBar != null)
750     {
751       progressBar.setProgressBar(msg, handle);
752     }
753   }
754
755   @Override
756   public void eps_actionPerformed(ActionEvent e)
757   {
758     throw new Error(
759             MessageManager
760                     .getString("error.eps_generation_not_implemented"));
761   }
762
763   @Override
764   public void png_actionPerformed(ActionEvent e)
765   {
766     throw new Error(
767             MessageManager
768                     .getString("error.png_generation_not_implemented"));
769   }
770
771   @Override
772   public void showHelp_actionPerformed(ActionEvent actionEvent)
773   {
774     try
775     {
776       BrowserLauncher
777               .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
778     } catch (IOException ex)
779     {
780     }
781   }
782
783   @Override
784   public AAStructureBindingModel getBinding()
785   {
786     return jmb;
787   }
788
789   /**
790    * Ask Chimera to save its session to the designated file path, or to a
791    * temporary file if the path is null. Returns the file path if successful,
792    * else null.
793    * 
794    * @param filepath
795    * @see getStateInfo
796    */
797   protected String saveSession(String filepath)
798   {
799     String pathUsed = filepath;
800     try
801     {
802       if (pathUsed == null)
803       {
804         File tempFile = File.createTempFile("chimera", ".py");
805         tempFile.deleteOnExit();
806         pathUsed = tempFile.getPath();
807       }
808       boolean result = jmb.saveSession(pathUsed);
809       if (result)
810       {
811         this.chimeraSessionFile = pathUsed;
812         return pathUsed;
813       }
814     } catch (IOException e)
815     {
816     }
817     return null;
818   }
819
820   /**
821    * Returns a string representing the state of the Chimera session. This is
822    * done by requesting Chimera to save its session to a temporary file, then
823    * reading the file contents. Returns an empty string on any error.
824    */
825   @Override
826   public String getStateInfo()
827   {
828     String sessionFile = saveSession(null);
829     if (sessionFile == null)
830     {
831       return "";
832     }
833     InputStream is = null;
834     try
835     {
836       File f = new File(sessionFile);
837       byte[] bytes = new byte[(int) f.length()];
838       is = new FileInputStream(sessionFile);
839       is.read(bytes);
840       return new String(bytes);
841     } catch (IOException e)
842     {
843       return "";
844     } finally
845     {
846       if (is != null)
847       {
848         try
849         {
850           is.close();
851         } catch (IOException e)
852         {
853           // ignore
854         }
855       }
856     }
857   }
858
859   @Override
860   protected void fitToWindow_actionPerformed()
861   {
862     jmb.focusView();
863   }
864
865   @Override
866   public ViewerType getViewerType()
867   {
868     return ViewerType.CHIMERA;
869   }
870
871   @Override
872   protected String getViewerName()
873   {
874     return "Chimera";
875   }
876
877   /**
878    * Sends commands to align structures according to associated alignment(s).
879    * 
880    * @return
881    */
882   @Override
883   protected String alignStructs_withAllAlignPanels()
884   {
885     String reply = super.alignStructs_withAllAlignPanels();
886     if (reply != null)
887     {
888       statusBar.setText("Superposition failed: " + reply);
889     }
890     return reply;
891   }
892 }