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