JAL-2826 added action performed for hiding collapsed sequences
[jalview.git] / src / jalview / gui / WebserviceInfo.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.jbgui.GWebserviceInfo;
24 import jalview.util.MessageManager;
25 import jalview.ws.WSClientI;
26
27 import java.awt.BorderLayout;
28 import java.awt.Color;
29 import java.awt.Font;
30 import java.awt.Graphics;
31 import java.awt.Graphics2D;
32 import java.awt.GridLayout;
33 import java.awt.Image;
34 import java.awt.MediaTracker;
35 import java.awt.event.ActionEvent;
36 import java.awt.image.BufferedImage;
37 import java.util.Vector;
38
39 import javax.swing.JComponent;
40 import javax.swing.JEditorPane;
41 import javax.swing.JInternalFrame;
42 import javax.swing.JPanel;
43 import javax.swing.JScrollPane;
44 import javax.swing.JTabbedPane;
45 import javax.swing.JTextArea;
46 import javax.swing.event.HyperlinkEvent;
47 import javax.swing.event.HyperlinkListener;
48 import javax.swing.text.html.HTMLEditorKit;
49 import javax.swing.text.html.StyleSheet;
50
51 /**
52  * Base class for web service client thread and gui TODO: create StAX parser to
53  * extract html body content reliably when preparing html formatted job statuses
54  * 
55  * @author $author$
56  * @version $Revision$
57  */
58 public class WebserviceInfo extends GWebserviceInfo
59         implements HyperlinkListener, IProgressIndicator
60 {
61
62   /** Job is Queued */
63   public static final int STATE_QUEUING = 0;
64
65   /** Job is Running */
66   public static final int STATE_RUNNING = 1;
67
68   /** Job has finished with no errors */
69   public static final int STATE_STOPPED_OK = 2;
70
71   /** Job has been cancelled with no errors */
72   public static final int STATE_CANCELLED_OK = 3;
73
74   /** job has stopped because of some error */
75   public static final int STATE_STOPPED_ERROR = 4;
76
77   /** job has failed because of some unavoidable service interruption */
78   public static final int STATE_STOPPED_SERVERERROR = 5;
79
80   int currentStatus = STATE_QUEUING;
81
82   Image image;
83
84   int angle = 0;
85
86   String title = "";
87
88   jalview.ws.WSClientI thisService;
89
90   boolean serviceIsCancellable;
91
92   JInternalFrame frame;
93
94   private IProgressIndicator progressBar;
95
96   @Override
97   public void setVisible(boolean aFlag)
98   {
99     super.setVisible(aFlag);
100     frame.setVisible(aFlag);
101   };
102
103   JTabbedPane subjobs = null;
104
105   java.util.Vector jobPanes = null;
106
107   private boolean serviceCanMergeResults = false;
108
109   private boolean viewResultsImmediatly = true;
110
111   /**
112    * Get
113    * 
114    * @param flag
115    *          to indicate if results will be shown in a new window as soon as
116    *          they are available.
117    */
118   public boolean isViewResultsImmediatly()
119   {
120     return viewResultsImmediatly;
121   }
122
123   /**
124    * Set
125    * 
126    * @param flag
127    *          to indicate if results will be shown in a new window as soon as
128    *          they are available.
129    */
130   public void setViewResultsImmediatly(boolean viewResultsImmediatly)
131   {
132     this.viewResultsImmediatly = viewResultsImmediatly;
133   }
134
135   private StyleSheet getStyleSheet(HTMLEditorKit editorKit)
136   {
137
138     // Copied blatantly from
139     // http://www.velocityreviews.com/forums/t132265-string-into-htmldocument.html
140     StyleSheet myStyleSheet = new StyleSheet();
141
142     myStyleSheet.addStyleSheet(editorKit.getStyleSheet());
143
144     editorKit.setStyleSheet(myStyleSheet);
145
146     /*
147      * Set the style sheet rules here by reading them from the constants
148      * interface.
149      */
150     /*
151      * for (int ix=0; ix<CSS_RULES.length; ix++) {
152      * 
153      * myStyleSheet.addRule(CSS_RULES[ix]);
154      * 
155      * }
156      */
157     return myStyleSheet;
158
159   }
160
161   // tabbed or not
162   public synchronized int addJobPane()
163   {
164     JScrollPane jobpane = new JScrollPane();
165     JComponent _progressText;
166     if (renderAsHtml)
167     {
168       JEditorPane progressText = new JEditorPane("text/html", "");
169       progressText.addHyperlinkListener(this);
170       _progressText = progressText;
171       // progressText.setFont(new java.awt.Font("Verdana", 0, 10));
172       // progressText.setBorder(null);
173       progressText.setEditable(false);
174       /*
175        * HTMLEditorKit myEditorKit = new HTMLEditorKit();
176        * 
177        * StyleSheet myStyleSheet = getStyleSheet(myEditorKit);
178        * 
179        * HTMLDocument tipDocument = (HTMLDocument)
180        * (myEditorKit.createDefaultDocument());
181        * 
182        * progressText.setDocument(tipDocument);
183        */progressText.setText("<html><h1>WS Job</h1></html>");
184     }
185     else
186     {
187       JTextArea progressText = new JTextArea();
188       _progressText = progressText;
189
190       progressText.setFont(new java.awt.Font("Verdana", 0, 10));
191       progressText.setBorder(null);
192       progressText.setEditable(false);
193       progressText.setText("WS Job");
194       progressText.setLineWrap(true);
195       progressText.setWrapStyleWord(true);
196     }
197     jobpane.setName("JobPane");
198     jobpane.getViewport().add(_progressText, null);
199     jobpane.setBorder(null);
200     if (jobPanes == null)
201     {
202       jobPanes = new Vector();
203     }
204     int newpane = jobPanes.size();
205     jobPanes.add(jobpane);
206
207     if (newpane == 0)
208     {
209       this.add(jobpane, BorderLayout.CENTER);
210     }
211     else
212     {
213       if (newpane == 1)
214       {
215         // revert to a tabbed pane.
216         JScrollPane firstpane;
217         this.remove(firstpane = (JScrollPane) jobPanes.get(0));
218         subjobs = new JTabbedPane();
219         this.add(subjobs, BorderLayout.CENTER);
220         subjobs.add(firstpane);
221         subjobs.setTitleAt(0, firstpane.getName());
222       }
223       subjobs.add(jobpane);
224     }
225     return newpane; // index for accessor methods below
226   }
227
228   /**
229    * Creates a new WebserviceInfo object.
230    * 
231    * @param title
232    *          short name and job type
233    * @param info
234    *          reference or other human readable description
235    * @param makeVisible
236    *          true to display the webservices window immediatly (otherwise need
237    *          to call setVisible(true))
238    */
239   public WebserviceInfo(String title, String info, boolean makeVisible)
240   {
241     init(title, info, 520, 500, makeVisible);
242   }
243
244   /**
245    * Creates a new WebserviceInfo object.
246    * 
247    * @param title
248    *          DOCUMENT ME!
249    * @param info
250    *          DOCUMENT ME!
251    * @param width
252    *          DOCUMENT ME!
253    * @param height
254    *          DOCUMENT ME!
255    */
256   public WebserviceInfo(String title, String info, int width, int height,
257           boolean makeVisible)
258   {
259     init(title, info, width, height, makeVisible);
260   }
261
262   /**
263    * DOCUMENT ME!
264    * 
265    * @return DOCUMENT ME!
266    */
267   public jalview.ws.WSClientI getthisService()
268   {
269     return thisService;
270   }
271
272   /**
273    * Update state of GUI based on client capabilities (like whether the job is
274    * cancellable, whether the 'merge results' button is shown.
275    * 
276    * @param newservice
277    *          service client to query for capabilities
278    */
279   public void setthisService(jalview.ws.WSClientI newservice)
280   {
281     thisService = newservice;
282     serviceIsCancellable = newservice.isCancellable();
283     frame.setClosable(!serviceIsCancellable);
284     serviceCanMergeResults = newservice.canMergeResults();
285     rebuildButtonPanel();
286   }
287
288   private void rebuildButtonPanel()
289   {
290     if (buttonPanel != null)
291     {
292       buttonPanel.removeAll();
293       if (serviceIsCancellable)
294       {
295         buttonPanel.add(cancel);
296         frame.setClosable(false);
297       }
298       else
299       {
300         frame.setClosable(true);
301       }
302     }
303   }
304
305   /**
306    * DOCUMENT ME!
307    * 
308    * @param title
309    *          DOCUMENT ME!
310    * @param info
311    *          DOCUMENT ME!
312    * @param width
313    *          DOCUMENT ME!
314    * @param height
315    *          DOCUMENT ME!
316    */
317   void init(String title, String info, int width, int height,
318           boolean makeVisible)
319   {
320     frame = new JInternalFrame();
321     frame.setContentPane(this);
322     Desktop.addInternalFrame(frame, title, makeVisible, width, height);
323     frame.setClosable(false);
324
325     progressBar = new ProgressBar(statusPanel, statusBar);
326
327     this.title = title;
328     setInfoText(info);
329
330     java.net.URL url = getClass()
331             .getResource("/images/Jalview_Logo_small.png");
332     image = java.awt.Toolkit.getDefaultToolkit().createImage(url);
333
334     MediaTracker mt = new MediaTracker(this);
335     mt.addImage(image, 0);
336
337     try
338     {
339       mt.waitForID(0);
340     } catch (Exception ex)
341     {
342     }
343
344     AnimatedPanel ap = new AnimatedPanel();
345     titlePanel.add(ap, BorderLayout.CENTER);
346
347     Thread thread = new Thread(ap, "AnimatedPanel");
348     thread.start();
349     final WebserviceInfo thisinfo = this;
350     frame.addInternalFrameListener(
351             new javax.swing.event.InternalFrameAdapter()
352             {
353               @Override
354               public void internalFrameClosed(
355                       javax.swing.event.InternalFrameEvent evt)
356               {
357                 // System.out.println("Shutting down webservice client");
358                 WSClientI service = thisinfo.getthisService();
359                 if (service != null && service.isCancellable())
360                 {
361                   service.cancelJob();
362                 }
363               };
364             });
365     frame.validate();
366
367   }
368
369   /**
370    * DOCUMENT ME!
371    * 
372    * @param status
373    *          integer status from state constants
374    */
375   public void setStatus(int status)
376   {
377     currentStatus = status;
378   }
379
380   /**
381    * subjob status indicator
382    * 
383    * @param jobpane
384    * @param status
385    */
386   public void setStatus(int jobpane, int status)
387   {
388     if (jobpane < 0 || jobpane >= jobPanes.size())
389     {
390       throw new Error(MessageManager.formatMessage(
391               "error.setstatus_called_non_existent_job_pane", new String[]
392               { Integer.valueOf(jobpane).toString() }));
393     }
394     switch (status)
395     {
396     case STATE_QUEUING:
397       setProgressName(jobpane + " - QUEUED", jobpane);
398       break;
399     case STATE_RUNNING:
400       setProgressName(jobpane + " - RUNNING", jobpane);
401       break;
402     case STATE_STOPPED_OK:
403       setProgressName(jobpane + " - FINISHED", jobpane);
404       break;
405     case STATE_CANCELLED_OK:
406       setProgressName(jobpane + " - CANCELLED", jobpane);
407       break;
408     case STATE_STOPPED_ERROR:
409       setProgressName(jobpane + " - BROKEN", jobpane);
410       break;
411     case STATE_STOPPED_SERVERERROR:
412       setProgressName(jobpane + " - ALERT", jobpane);
413       break;
414     default:
415       setProgressName(jobpane + " - UNKNOWN STATE", jobpane);
416     }
417   }
418
419   /**
420    * DOCUMENT ME!
421    * 
422    * @return DOCUMENT ME!
423    */
424   public String getInfoText()
425   {
426     return infoText.getText();
427   }
428
429   /**
430    * DOCUMENT ME!
431    * 
432    * @param text
433    *          DOCUMENT ME!
434    */
435   public void setInfoText(String text)
436   {
437     infoText.setText(text);
438   }
439
440   /**
441    * DOCUMENT ME!
442    * 
443    * @param text
444    *          DOCUMENT ME!
445    */
446   public void appendInfoText(String text)
447   {
448     infoText.append(text);
449   }
450
451   /**
452    * DOCUMENT ME!
453    * 
454    * @return DOCUMENT ME!
455    */
456   public String getProgressText(int which)
457   {
458     if (jobPanes == null)
459     {
460       addJobPane();
461     }
462     if (renderAsHtml)
463     {
464       return ((JEditorPane) ((JScrollPane) jobPanes.get(which))
465               .getViewport().getComponent(0)).getText();
466     }
467     else
468     {
469       return ((JTextArea) ((JScrollPane) jobPanes.get(which)).getViewport()
470               .getComponent(0)).getText();
471     }
472   }
473
474   /**
475    * DOCUMENT ME!
476    * 
477    * @param text
478    *          DOCUMENT ME!
479    */
480   public void setProgressText(int which, String text)
481   {
482     if (jobPanes == null)
483     {
484       addJobPane();
485     }
486     if (renderAsHtml)
487     {
488       ((JEditorPane) ((JScrollPane) jobPanes.get(which)).getViewport()
489               .getComponent(0)).setText(ensureHtmlTagged(text));
490     }
491     else
492     {
493       ((JTextArea) ((JScrollPane) jobPanes.get(which)).getViewport()
494               .getComponent(0)).setText(text);
495     }
496   }
497
498   /**
499    * extract content from &lt;body&gt; content &lt;/body&gt;
500    * 
501    * @param text
502    * @param leaveFirst
503    *          - set to leave the initial html tag intact
504    * @param leaveLast
505    *          - set to leave the final html tag intact
506    * @return
507    */
508   private String getHtmlFragment(String text, boolean leaveFirst,
509           boolean leaveLast)
510   {
511     if (text == null)
512     {
513       return null;
514     }
515     String lowertxt = text.toLowerCase();
516     int htmlpos = leaveFirst ? -1 : lowertxt.indexOf("<body");
517
518     int htmlend = leaveLast ? -1 : lowertxt.indexOf("</body");
519     int htmlpose = lowertxt.indexOf(">", htmlpos),
520             htmlende = lowertxt.indexOf(">", htmlend);
521     if (htmlend == -1 && htmlpos == -1)
522     {
523       return text;
524     }
525     if (htmlend > -1)
526     {
527       return text.substring((htmlpos == -1 ? 0 : htmlpose + 1), htmlend);
528     }
529     return text.substring(htmlpos == -1 ? 0 : htmlpose + 1);
530   }
531
532   /**
533    * very simple routine for adding/ensuring html tags are present in text.
534    * 
535    * @param text
536    * @return properly html tag enclosed text
537    */
538   private String ensureHtmlTagged(String text)
539   {
540     if (text == null)
541     {
542       return "";
543     }
544     String lowertxt = text.toLowerCase();
545     int htmlpos = lowertxt.indexOf("<body");
546     int htmlend = lowertxt.indexOf("</body");
547     int doctype = lowertxt.indexOf("<!doctype");
548     int xmltype = lowertxt.indexOf("<?xml");
549     if (htmlend == -1)
550     {
551       text = text + "</body></html>";
552     }
553     if (htmlpos > -1)
554     {
555       if ((doctype > -1 && htmlpos > doctype)
556               || (xmltype > -1 && htmlpos > xmltype))
557       {
558         text = "<html><head></head><body>\n" + text.substring(htmlpos - 1);
559       }
560     }
561     else
562     {
563       text = "<html><head></head><body>\n" + text;
564     }
565     if (text.indexOf("<meta") > -1)
566     {
567       System.err
568               .println("HTML COntent: \n" + text + "<< END HTML CONTENT\n");
569
570     }
571     return text;
572   }
573
574   /**
575    * DOCUMENT ME!
576    * 
577    * @param text
578    *          DOCUMENT ME!
579    */
580   public void appendProgressText(int which, String text)
581   {
582     if (jobPanes == null)
583     {
584       addJobPane();
585     }
586     if (renderAsHtml)
587     {
588       String txt = getHtmlFragment(
589               ((JEditorPane) ((JScrollPane) jobPanes.get(which))
590                       .getViewport().getComponent(0)).getText(),
591               true, false);
592       ((JEditorPane) ((JScrollPane) jobPanes.get(which)).getViewport()
593               .getComponent(0))
594                       .setText(ensureHtmlTagged(
595                               txt + getHtmlFragment(text, false, true)));
596     }
597     else
598     {
599       ((JTextArea) ((JScrollPane) jobPanes.get(which)).getViewport()
600               .getComponent(0)).append(text);
601     }
602   }
603
604   /**
605    * setProgressText(0, text)
606    */
607   public void setProgressText(String text)
608   {
609     setProgressText(0, text);
610   }
611
612   /**
613    * appendProgressText(0, text)
614    */
615   public void appendProgressText(String text)
616   {
617     appendProgressText(0, text);
618   }
619
620   /**
621    * getProgressText(0)
622    */
623   public String getProgressText()
624   {
625     return getProgressText(0);
626   }
627
628   /**
629    * get the tab title for a subjob
630    * 
631    * @param which
632    *          int
633    * @return String
634    */
635   public String getProgressName(int which)
636   {
637     if (jobPanes == null)
638     {
639       addJobPane();
640     }
641     if (subjobs != null)
642     {
643       return subjobs.getTitleAt(which);
644     }
645     else
646     {
647       return ((JScrollPane) jobPanes.get(which)).getViewport()
648               .getComponent(0).getName();
649     }
650   }
651
652   /**
653    * set the tab title for a subjob
654    * 
655    * @param name
656    *          String
657    * @param which
658    *          int
659    */
660   public void setProgressName(String name, int which)
661   {
662     if (subjobs != null)
663     {
664       subjobs.setTitleAt(which, name);
665       subjobs.revalidate();
666       subjobs.repaint();
667     }
668     JScrollPane c = (JScrollPane) jobPanes.get(which);
669     c.getViewport().getComponent(0).setName(name);
670     c.repaint();
671   }
672
673   /**
674    * Gui action for cancelling the current job, if possible.
675    * 
676    * @param e
677    *          DOCUMENT ME!
678    */
679   @Override
680   protected void cancel_actionPerformed(ActionEvent e)
681   {
682     if (!serviceIsCancellable)
683     {
684       // JBPNote : TODO: We should REALLY just tell the WSClientI to cancel
685       // anyhow - it has to stop threads and clean up
686       // JBPNote : TODO: Instead of a warning, we should have an optional 'Are
687       // you sure?' prompt
688       warnUser(
689               MessageManager.getString(
690                       "warn.job_cannot_be_cancelled_close_window"),
691               MessageManager.getString("action.cancel_job"));
692     }
693     else
694     {
695       thisService.cancelJob();
696     }
697     frame.setClosable(true);
698   }
699
700   /**
701    * Spawns a thread that pops up a warning dialog box with the given message
702    * and title.
703    * 
704    * @param message
705    * @param title
706    */
707   public void warnUser(final String message, final String title)
708   {
709     javax.swing.SwingUtilities.invokeLater(new Runnable()
710     {
711       @Override
712       public void run()
713       {
714         JvOptionPane.showInternalMessageDialog(Desktop.desktop, message,
715                 title, JvOptionPane.WARNING_MESSAGE);
716
717       }
718     });
719   }
720
721   /**
722    * Set up GUI for user to get at results - and possibly automatically display
723    * them if viewResultsImmediatly is set.
724    */
725   public void setResultsReady()
726   {
727     frame.setClosable(true);
728     buttonPanel.remove(cancel);
729     buttonPanel.add(showResultsNewFrame);
730     if (serviceCanMergeResults)
731     {
732       buttonPanel.add(mergeResults);
733       buttonPanel.setLayout(new GridLayout(2, 1, 5, 5));
734     }
735     buttonPanel.validate();
736     validate();
737     if (viewResultsImmediatly)
738     {
739       showResultsNewFrame.doClick();
740     }
741   }
742
743   /**
744    * called when job has finished but no result objects can be passed back to
745    * user
746    */
747   public void setFinishedNoResults()
748   {
749     frame.setClosable(true);
750     buttonPanel.remove(cancel);
751     buttonPanel.validate();
752     validate();
753   }
754
755   class AnimatedPanel extends JPanel implements Runnable
756   {
757     long startTime = 0;
758
759     BufferedImage offscreen;
760
761     @Override
762     public void run()
763     {
764       startTime = System.currentTimeMillis();
765
766       while (currentStatus < STATE_STOPPED_OK)
767       {
768         try
769         {
770           Thread.sleep(50);
771
772           int units = (int) ((System.currentTimeMillis() - startTime)
773                   / 10f);
774           angle += units;
775           angle %= 360;
776           startTime = System.currentTimeMillis();
777
778           if (currentStatus >= STATE_STOPPED_OK)
779           {
780             angle = 0;
781           }
782
783           repaint();
784         } catch (Exception ex)
785         {
786         }
787       }
788
789       cancel.setEnabled(false);
790     }
791
792     void drawPanel()
793     {
794       if (offscreen == null || offscreen.getWidth(this) != getWidth()
795               || offscreen.getHeight(this) != getHeight())
796       {
797         offscreen = new BufferedImage(getWidth(), getHeight(),
798                 BufferedImage.TYPE_INT_ARGB);
799       }
800
801       Graphics2D g = (Graphics2D) offscreen.getGraphics();
802
803       g.setColor(Color.white);
804       g.fillRect(0, 0, getWidth(), getHeight());
805
806       g.setFont(new Font("Arial", Font.BOLD, 12));
807       g.setColor(Color.black);
808
809       switch (currentStatus)
810       {
811       case STATE_QUEUING:
812         g.drawString(
813                 title.concat(" - ").concat(
814                         MessageManager.getString("label.state_queueing")),
815                 60, 30);
816
817         break;
818
819       case STATE_RUNNING:
820         g.drawString(
821                 title.concat(" - ").concat(
822                         MessageManager.getString("label.state_running")),
823                 60, 30);
824
825         break;
826
827       case STATE_STOPPED_OK:
828         g.drawString(
829                 title.concat(" - ").concat(
830                         MessageManager.getString("label.state_completed")),
831                 60, 30);
832
833         break;
834
835       case STATE_CANCELLED_OK:
836         g.drawString(
837                 title.concat(" - ")
838                         .concat(MessageManager
839                                 .getString("label.state_job_cancelled")),
840                 60, 30);
841
842         break;
843
844       case STATE_STOPPED_ERROR:
845         g.drawString(
846                 title.concat(" - ").concat(
847                         MessageManager.getString("label.state_job_error")),
848                 60, 30);
849
850         break;
851
852       case STATE_STOPPED_SERVERERROR:
853         g.drawString(
854                 title.concat(" - ")
855                         .concat(MessageManager
856                                 .getString("label.server_error_try_later")),
857                 60, 30);
858
859         break;
860       }
861
862       if (image != null)
863       {
864         int x = image.getWidth(this) / 2, y = image.getHeight(this) / 2;
865         g.rotate(Math.toRadians(angle), 10 + x, 10 + y);
866         g.drawImage(image, 10, 10, this);
867         g.rotate(-Math.toRadians(angle), 10 + x, 10 + y);
868       }
869     }
870
871     @Override
872     public void paintComponent(Graphics g1)
873     {
874       drawPanel();
875
876       g1.drawImage(offscreen, 0, 0, this);
877     }
878   }
879
880   boolean renderAsHtml = false;
881
882   public void setRenderAsHtml(boolean b)
883   {
884     renderAsHtml = b;
885   }
886
887   @Override
888   public void hyperlinkUpdate(HyperlinkEvent e)
889   {
890     Desktop.hyperlinkUpdate(e);
891   }
892
893   /*
894    * (non-Javadoc)
895    * 
896    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
897    */
898   @Override
899   public void setProgressBar(String message, long id)
900   {
901     progressBar.setProgressBar(message, id);
902   }
903
904   @Override
905   public void registerHandler(final long id,
906           final IProgressIndicatorHandler handler)
907   {
908     progressBar.registerHandler(id, handler);
909   }
910
911   /**
912    * 
913    * @return true if any progress bars are still active
914    */
915   @Override
916   public boolean operationInProgress()
917   {
918     return progressBar.operationInProgress();
919   }
920 }