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