83926066c4aa18af9cf55787e7fac4e7f3e69006
[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     if (!Platform.isJS())
358     {
359         jmb.startChimeraListener();
360     }
361   }
362
363   /**
364    * Show only the selected chain(s) in the viewer
365    */
366   @Override
367   void showSelectedChains()
368   {
369     List<String> toshow = new ArrayList<>();
370     for (int i = 0; i < chainMenu.getItemCount(); i++)
371     {
372       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
373       {
374         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
375         if (item.isSelected())
376         {
377           toshow.add(item.getText());
378         }
379       }
380     }
381     jmb.showChains(toshow);
382   }
383
384   /**
385    * Close down this instance of Jalview's Chimera viewer, giving the user the
386    * option to close the associated Chimera window (process). They may wish to
387    * keep it open until they have had an opportunity to save any work.
388    * 
389    * @param closeChimera
390    *          if true, close any linked Chimera process; if false, prompt first
391    */
392   @Override
393   public void closeViewer(boolean closeChimera)
394   {
395     if (jmb != null && jmb.isChimeraRunning())
396     {
397       if (!closeChimera)
398       {
399         String prompt = MessageManager
400                 .formatMessage("label.confirm_close_chimera", new Object[]
401                 { jmb.getViewerTitle(getViewerName(), false) });
402         prompt = JvSwingUtils.wrapTooltip(true, prompt);
403         int confirm = JvOptionPane.showConfirmDialog(this, prompt,
404                 MessageManager.getString("label.close_viewer"),
405                 JvOptionPane.YES_NO_CANCEL_OPTION);
406         /*
407          * abort closure if user hits escape or Cancel
408          */
409         if (confirm == JvOptionPane.CANCEL_OPTION
410                 || confirm == JvOptionPane.CLOSED_OPTION)
411         {
412           return;
413         }
414         closeChimera = confirm == JvOptionPane.YES_OPTION;
415       }
416       jmb.closeViewer(closeChimera);
417     }
418     setAlignmentPanel(null);
419     _aps.clear();
420     _alignwith.clear();
421     _colourwith.clear();
422     // TODO: check for memory leaks where instance isn't finalised because jmb
423     // holds a reference to the window
424     jmb = null;
425     dispose();
426   }
427
428   /**
429    * Open any newly added PDB structures in Chimera, having first fetched data
430    * from PDB (if not already saved).
431    */
432   @Override
433   public void run()
434   {
435     _started = true;
436     // todo - record which pdbids were successfully imported.
437     StringBuilder errormsgs = new StringBuilder(128);
438     StringBuilder files = new StringBuilder(128);
439     List<PDBEntry> filePDB = new ArrayList<>();
440     List<Integer> filePDBpos = new ArrayList<>();
441     PDBEntry thePdbEntry = null;
442     StructureFile pdb = null;
443     try
444     {
445       String[] curfiles = jmb.getStructureFiles(); // files currently in viewer
446       // TODO: replace with reference fetching/transfer code (validate PDBentry
447       // as a DBRef?)
448       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
449       {
450         String file = null;
451         thePdbEntry = jmb.getPdbEntry(pi);
452         if (thePdbEntry.getFile() == null)
453         {
454           /*
455            * Retrieve PDB data, save to file, attach to PDBEntry
456            */
457           file = fetchPdbFile(thePdbEntry);
458           if (file == null)
459           {
460             errormsgs.append("'" + thePdbEntry.getId() + "' ");
461           }
462         }
463         else
464         {
465           /*
466            * Got file already - ignore if already loaded in Chimera.
467            */
468           file = new File(thePdbEntry.getFile()).getAbsoluteFile()
469                   .getPath();
470           if (curfiles != null && curfiles.length > 0)
471           {
472             addingStructures = true; // already files loaded.
473             for (int c = 0; c < curfiles.length; c++)
474             {
475               if (curfiles[c].equals(file))
476               {
477                 file = null;
478                 break;
479               }
480             }
481           }
482         }
483         if (file != null)
484         {
485           filePDB.add(thePdbEntry);
486           filePDBpos.add(Integer.valueOf(pi));
487           files.append(" \"" + Platform.escapeString(file) + "\"");
488         }
489       }
490     } catch (OutOfMemoryError oomerror)
491     {
492       new OOMWarning("Retrieving PDB files: " + thePdbEntry.getId(),
493               oomerror);
494     } catch (Exception ex)
495     {
496       ex.printStackTrace();
497       errormsgs.append(
498               "When retrieving pdbfiles for '" + thePdbEntry.getId() + "'");
499     }
500     if (errormsgs.length() > 0)
501     {
502
503       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
504               MessageManager.formatMessage(
505                       "label.pdb_entries_couldnt_be_retrieved", new Object[]
506                       { errormsgs.toString() }),
507               MessageManager.getString("label.couldnt_load_file"),
508               JvOptionPane.ERROR_MESSAGE);
509     }
510
511     if (files.length() > 0)
512     {
513       jmb.setFinishedInit(false);
514       if (!addingStructures)
515       {
516         try
517         {
518           initChimera();
519         } catch (Exception ex)
520         {
521           Cache.log.error("Couldn't open Chimera viewer!", ex);
522         }
523       }
524       int num = -1;
525       for (PDBEntry pe : filePDB)
526       {
527         num++;
528         if (pe.getFile() != null)
529         {
530           try
531           {
532             int pos = filePDBpos.get(num).intValue();
533             long startTime = startProgressBar(getViewerName() + " "
534                     + MessageManager.getString("status.opening_file_for")
535                     + " " + pe.getId());
536             jmb.openFile(pe);
537             jmb.addSequence(pos, jmb.getSequence()[pos]);
538             File fl = new File(pe.getFile());
539             DataSourceType protocol = DataSourceType.URL;
540             try
541             {
542               if (fl.exists())
543               {
544                 protocol = DataSourceType.FILE;
545               }
546             } catch (Throwable e)
547             {
548             } finally
549             {
550               stopProgressBar("", startTime);
551             }
552             // Explicitly map to the filename used by Chimera ;
553
554             pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
555                     jmb.getChains()[pos], pe.getFile(), protocol,
556                     progressBar);
557             stashFoundChains(pdb, pe.getFile());
558
559           } catch (OutOfMemoryError oomerror)
560           {
561             new OOMWarning(
562                     "When trying to open and map structures from Chimera!",
563                     oomerror);
564           } catch (Exception ex)
565           {
566             Cache.log.error(
567                     "Couldn't open " + pe.getFile() + " in Chimera viewer!",
568                     ex);
569           } finally
570           {
571             Cache.log.debug("File locations are " + files);
572           }
573         }
574       }
575
576       jmb.refreshGUI();
577       jmb.setFinishedInit(true);
578       jmb.setLoadingFromArchive(false);
579
580       /*
581        * ensure that any newly discovered features (e.g. RESNUM)
582        * are added to any open feature settings dialog
583        */
584       FeatureRenderer fr = getBinding().getFeatureRenderer(null);
585       if (fr != null)
586       {
587         fr.featuresAdded();
588       }
589
590       // refresh the sequence colours for the new structure(s)
591       for (AlignmentPanel ap : _colourwith)
592       {
593         jmb.updateColours(ap);
594       }
595       // do superposition if asked to
596       if (alignAddedStructures)
597       {
598         new Thread(new Runnable()
599         {
600           @Override
601           public void run()
602           {
603             alignStructs_withAllAlignPanels();
604           }
605         }).start();
606       }
607       addingStructures = false;
608     }
609     _started = false;
610     worker = null;
611   }
612
613   /**
614    * Fetch PDB data and save to a local file. Returns the full path to the file,
615    * or null if fetch fails. TODO: refactor to common with Jmol ? duplication
616    * 
617    * @param processingEntry
618    * @return
619    * @throws Exception
620    */
621
622   private void stashFoundChains(StructureFile pdb, String file)
623   {
624     for (int i = 0; i < pdb.getChains().size(); i++)
625     {
626       String chid = new String(
627               pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
628       jmb.getChainNames().add(chid);
629       jmb.getChainFile().put(chid, file);
630     }
631   }
632
633   private String fetchPdbFile(PDBEntry processingEntry) throws Exception
634   {
635     String filePath = null;
636     Pdb pdbclient = new Pdb();
637     AlignmentI pdbseq = null;
638     String pdbid = processingEntry.getId();
639     long handle = System.currentTimeMillis()
640             + Thread.currentThread().hashCode();
641
642     /*
643      * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
644      */
645     String msg = MessageManager.formatMessage("status.fetching_pdb",
646             new Object[]
647             { pdbid });
648     getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
649     // long hdl = startProgressBar(MessageManager.formatMessage(
650     // "status.fetching_pdb", new Object[]
651     // { pdbid }));
652     try
653     {
654       pdbseq = pdbclient.getSequenceRecords(pdbid);
655     } catch (OutOfMemoryError oomerror)
656     {
657       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
658     } finally
659     {
660       msg = pdbid + " " + MessageManager.getString("label.state_completed");
661       getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
662       // stopProgressBar(msg, hdl);
663     }
664     /*
665      * If PDB data were saved and are not invalid (empty alignment), return the
666      * file path.
667      */
668     if (pdbseq != null && pdbseq.getHeight() > 0)
669     {
670       // just use the file name from the first sequence's first PDBEntry
671       filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
672               .elementAt(0).getFile()).getAbsolutePath();
673       processingEntry.setFile(filePath);
674     }
675     return filePath;
676   }
677
678   /**
679    * Convenience method to update the progress bar if there is one. Be sure to
680    * call stopProgressBar with the returned handle to remove the message.
681    * 
682    * @param msg
683    * @param handle
684    */
685   public long startProgressBar(String msg)
686   {
687     // TODO would rather have startProgress/stopProgress as the
688     // IProgressIndicator interface
689     long tm = random.nextLong();
690     if (progressBar != null)
691     {
692       progressBar.setProgressBar(msg, tm);
693     }
694     return tm;
695   }
696
697   /**
698    * End the progress bar with the specified handle, leaving a message (if not
699    * null) on the status bar
700    * 
701    * @param msg
702    * @param handle
703    */
704   public void stopProgressBar(String msg, long handle)
705   {
706     if (progressBar != null)
707     {
708       progressBar.setProgressBar(msg, handle);
709     }
710   }
711
712   @Override
713   public void makePDBImage(TYPE imageType)
714   {
715     throw new UnsupportedOperationException(
716             "Image export for Chimera is not implemented");
717   }
718
719   @Override
720   public void showHelp_actionPerformed(ActionEvent actionEvent)
721   {
722     try
723     {
724       BrowserLauncher
725               .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
726     } catch (IOException ex)
727     {
728     }
729   }
730
731   @Override
732   public AAStructureBindingModel getBinding()
733   {
734     return jmb;
735   }
736
737   /**
738    * Ask Chimera to save its session to the designated file path, or to a
739    * temporary file if the path is null. Returns the file path if successful,
740    * else null.
741    * 
742    * @param filepath
743    * @see getStateInfo
744    */
745   protected String saveSession(String filepath)
746   {
747     String pathUsed = filepath;
748     try
749     {
750       if (pathUsed == null)
751       {
752         File tempFile = File.createTempFile("chimera", ".py");
753         tempFile.deleteOnExit();
754         pathUsed = tempFile.getPath();
755       }
756       boolean result = jmb.saveSession(pathUsed);
757       if (result)
758       {
759         this.chimeraSessionFile = pathUsed;
760         return pathUsed;
761       }
762     } catch (IOException e)
763     {
764     }
765     return null;
766   }
767
768   /**
769    * Returns a string representing the state of the Chimera session. This is
770    * done by requesting Chimera to save its session to a temporary file, then
771    * reading the file contents. Returns an empty string on any error.
772    */
773   @Override
774   public String getStateInfo()
775   {
776     String sessionFile = saveSession(null);
777     if (sessionFile == null)
778     {
779       return "";
780     }
781     InputStream is = null;
782     try
783     {
784       File f = new File(sessionFile);
785       byte[] bytes = new byte[(int) f.length()];
786       is = new FileInputStream(sessionFile);
787       is.read(bytes);
788       return new String(bytes);
789     } catch (IOException e)
790     {
791       return "";
792     } finally
793     {
794       if (is != null)
795       {
796         try
797         {
798           is.close();
799         } catch (IOException e)
800         {
801           // ignore
802         }
803       }
804     }
805   }
806
807   @Override
808   protected void fitToWindow_actionPerformed()
809   {
810     jmb.focusView();
811   }
812
813   @Override
814   public ViewerType getViewerType()
815   {
816     return ViewerType.CHIMERA;
817   }
818
819   @Override
820   protected String getViewerName()
821   {
822     return "Chimera";
823   }
824
825   /**
826    * Sends commands to align structures according to associated alignment(s).
827    * 
828    * @return
829    */
830   @Override
831   protected String alignStructs_withAllAlignPanels()
832   {
833     String reply = super.alignStructs_withAllAlignPanels();
834     if (reply != null)
835     {
836       statusBar.setText("Superposition failed: " + reply);
837     }
838     return reply;
839   }
840
841   @Override
842   protected IProgressIndicator getIProgressIndicator()
843   {
844     return progressBar;
845   }
846 }