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