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