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