2fc08e16d44a6383995c9b08a5ad46a0372cc9db
[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.JOptionPane;
43 import javax.swing.JPanel;
44 import javax.swing.JScrollPane;
45 import javax.swing.JTabbedPane;
46 import javax.swing.JTextArea;
47 import javax.swing.event.HyperlinkEvent;
48 import javax.swing.event.HyperlinkListener;
49 import javax.swing.text.html.HTMLEditorKit;
50 import javax.swing.text.html.StyleSheet;
51
52 /**
53  * Base class for web service client thread and gui TODO: create StAX parser to
54  * extract html body content reliably when preparing html formatted job statuses
55  * 
56  * @author $author$
57  * @version $Revision$
58  */
59 public class WebserviceInfo extends GWebserviceInfo
60         implements HyperlinkListener, IProgressIndicator
61 {
62
63   /** Job is Queued */
64   public static final int STATE_QUEUING = 0;
65
66   /** Job is Running */
67   public static final int STATE_RUNNING = 1;
68
69   /** Job has finished with no errors */
70   public static final int STATE_STOPPED_OK = 2;
71
72   /** Job has been cancelled with no errors */
73   public static final int STATE_CANCELLED_OK = 3;
74
75   /** job has stopped because of some error */
76   public static final int STATE_STOPPED_ERROR = 4;
77
78   /** job has failed because of some unavoidable service interruption */
79   public static final int STATE_STOPPED_SERVERERROR = 5;
80
81   int currentStatus = STATE_QUEUING;
82
83   Image image;
84
85   int angle = 0;
86
87   String title = "";
88
89   jalview.ws.WSClientI thisService;
90
91   boolean serviceIsCancellable;
92
93   JInternalFrame frame;
94
95   private IProgressIndicator progressBar;
96
97   @Override
98   public void setVisible(boolean aFlag)
99   {
100     super.setVisible(aFlag);
101     frame.setVisible(aFlag);
102   };
103
104   JTabbedPane subjobs = null;
105
106   java.util.Vector jobPanes = null;
107
108   private boolean serviceCanMergeResults = false;
109
110   private boolean viewResultsImmediatly = true;
111
112   /**
113    * Get
114    * 
115    * @param flag
116    *          to indicate if results will be shown in a new window as soon as
117    *          they are available.
118    */
119   public boolean isViewResultsImmediatly()
120   {
121     return viewResultsImmediatly;
122   }
123
124   /**
125    * Set
126    * 
127    * @param flag
128    *          to indicate if results will be shown in a new window as soon as
129    *          they are available.
130    */
131   public void setViewResultsImmediatly(boolean viewResultsImmediatly)
132   {
133     this.viewResultsImmediatly = viewResultsImmediatly;
134   }
135
136   private StyleSheet getStyleSheet(HTMLEditorKit editorKit)
137   {
138
139     // Copied blatantly from
140     // http://www.velocityreviews.com/forums/t132265-string-into-htmldocument.html
141     StyleSheet myStyleSheet = new StyleSheet();
142
143     myStyleSheet.addStyleSheet(editorKit.getStyleSheet());
144
145     editorKit.setStyleSheet(myStyleSheet);
146
147     /*
148      * Set the style sheet rules here by reading them from the constants
149      * interface.
150      */
151     /*
152      * for (int ix=0; ix<CSS_RULES.length; ix++) {
153      * 
154      * myStyleSheet.addRule(CSS_RULES[ix]);
155      * 
156      * }
157      */
158     return myStyleSheet;
159
160   }
161
162   // tabbed or not
163   public synchronized int addJobPane()
164   {
165     JScrollPane jobpane = new JScrollPane();
166     JComponent _progressText;
167     if (renderAsHtml)
168     {
169       JEditorPane progressText = new JEditorPane("text/html", "");
170       progressText.addHyperlinkListener(this);
171       _progressText = progressText;
172       // progressText.setFont(new java.awt.Font("Verdana", 0, 10));
173       // progressText.setBorder(null);
174       progressText.setEditable(false);
175       /*
176        * HTMLEditorKit myEditorKit = new HTMLEditorKit();
177        * 
178        * StyleSheet myStyleSheet = getStyleSheet(myEditorKit);
179        * 
180        * HTMLDocument tipDocument = (HTMLDocument)
181        * (myEditorKit.createDefaultDocument());
182        * 
183        * progressText.setDocument(tipDocument);
184        */progressText.setText("<html><h1>WS Job</h1></html>");
185     }
186     else
187     {
188       JTextArea progressText = new JTextArea();
189       _progressText = progressText;
190
191       progressText.setFont(new java.awt.Font("Verdana", 0, 10));
192       progressText.setBorder(null);
193       progressText.setEditable(false);
194       progressText.setText("WS Job");
195       progressText.setLineWrap(true);
196       progressText.setWrapStyleWord(true);
197     }
198     jobpane.setName("JobPane");
199     jobpane.getViewport().add(_progressText, null);
200     jobpane.setBorder(null);
201     if (jobPanes == null)
202     {
203       jobPanes = new Vector();
204     }
205     int newpane = jobPanes.size();
206     jobPanes.add(jobpane);
207
208     if (newpane == 0)
209     {
210       this.add(jobpane, BorderLayout.CENTER);
211     }
212     else
213     {
214       if (newpane == 1)
215       {
216         // revert to a tabbed pane.
217         JScrollPane firstpane;
218         this.remove(firstpane = (JScrollPane) jobPanes.get(0));
219         subjobs = new JTabbedPane();
220         this.add(subjobs, BorderLayout.CENTER);
221         subjobs.add(firstpane);
222         subjobs.setTitleAt(0, firstpane.getName());
223       }
224       subjobs.add(jobpane);
225     }
226     return newpane; // index for accessor methods below
227   }
228
229   /**
230    * Creates a new WebserviceInfo object.
231    * 
232    * @param title
233    *          short name and job type
234    * @param info
235    *          reference or other human readable description
236    * @param makeVisible
237    *          true to display the webservices window immediatly (otherwise need
238    *          to call setVisible(true))
239    */
240   public WebserviceInfo(String title, String info, boolean makeVisible)
241   {
242     init(title, info, 520, 500, makeVisible);
243   }
244
245   /**
246    * Creates a new WebserviceInfo object.
247    * 
248    * @param title
249    *          DOCUMENT ME!
250    * @param info
251    *          DOCUMENT ME!
252    * @param width
253    *          DOCUMENT ME!
254    * @param height
255    *          DOCUMENT ME!
256    */
257   public WebserviceInfo(String title, String info, int width, int height,
258           boolean makeVisible)
259   {
260     init(title, info, width, height, makeVisible);
261   }
262
263   /**
264    * DOCUMENT ME!
265    * 
266    * @return DOCUMENT ME!
267    */
268   public jalview.ws.WSClientI getthisService()
269   {
270     return thisService;
271   }
272
273   /**
274    * Update state of GUI based on client capabilities (like whether the job is
275    * cancellable, whether the 'merge results' button is shown.
276    * 
277    * @param newservice
278    *          service client to query for capabilities
279    */
280   public void setthisService(jalview.ws.WSClientI newservice)
281   {
282     thisService = newservice;
283     serviceIsCancellable = newservice.isCancellable();
284     frame.setClosable(!serviceIsCancellable);
285     serviceCanMergeResults = newservice.canMergeResults();
286     rebuildButtonPanel();
287   }
288
289   private void rebuildButtonPanel()
290   {
291     if (buttonPanel != null)
292     {
293       buttonPanel.removeAll();
294       if (serviceIsCancellable)
295       {
296         buttonPanel.add(cancel);
297         frame.setClosable(false);
298       }
299       else
300       {
301         frame.setClosable(true);
302       }
303     }
304   }
305
306   /**
307    * DOCUMENT ME!
308    * 
309    * @param title
310    *          DOCUMENT ME!
311    * @param info
312    *          DOCUMENT ME!
313    * @param width
314    *          DOCUMENT ME!
315    * @param height
316    *          DOCUMENT ME!
317    */
318   void init(String title, String info, int width, int height,
319           boolean makeVisible)
320   {
321     frame = new JInternalFrame();
322     frame.setContentPane(this);
323     Desktop.addInternalFrame(frame, title, makeVisible, width, height);
324     frame.setClosable(false);
325
326     progressBar = new ProgressBar(statusPanel, statusBar);
327
328     this.title = title;
329     setInfoText(info);
330
331     java.net.URL url = getClass()
332             .getResource("/images/Jalview_Logo_small.png");
333     image = java.awt.Toolkit.getDefaultToolkit().createImage(url);
334
335     MediaTracker mt = new MediaTracker(this);
336     mt.addImage(image, 0);
337
338     try
339     {
340       mt.waitForID(0);
341     } catch (Exception ex)
342     {
343     }
344
345     AnimatedPanel ap = new AnimatedPanel();
346     titlePanel.add(ap, BorderLayout.CENTER);
347
348     Thread thread = new Thread(ap);
349     thread.start();
350     final WebserviceInfo thisinfo = this;
351     frame.addInternalFrameListener(
352             new javax.swing.event.InternalFrameAdapter()
353             {
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   protected void cancel_actionPerformed(ActionEvent e)
680   {
681     if (!serviceIsCancellable)
682     {
683       // JBPNote : TODO: We should REALLY just tell the WSClientI to cancel
684       // anyhow - it has to stop threads and clean up
685       // JBPNote : TODO: Instead of a warning, we should have an optional 'Are
686       // you sure?' prompt
687       warnUser(
688               MessageManager.getString(
689                       "warn.job_cannot_be_cancelled_close_window"),
690               MessageManager.getString("action.cancel_job"));
691     }
692     else
693     {
694       thisService.cancelJob();
695     }
696     frame.setClosable(true);
697   }
698
699   /**
700    * Spawns a thread that pops up a warning dialog box with the given message
701    * and title.
702    * 
703    * @param message
704    * @param title
705    */
706   public void warnUser(final String message, final String title)
707   {
708     javax.swing.SwingUtilities.invokeLater(new Runnable()
709     {
710       public void run()
711       {
712         JvOptionPane.showInternalMessageDialog(Desktop.desktop, message,
713                 title, JvOptionPane.WARNING_MESSAGE);
714
715       }
716     });
717   }
718
719   /**
720    * Set up GUI for user to get at results - and possibly automatically display
721    * them if viewResultsImmediatly is set.
722    */
723   public void setResultsReady()
724   {
725     frame.setClosable(true);
726     buttonPanel.remove(cancel);
727     buttonPanel.add(showResultsNewFrame);
728     if (serviceCanMergeResults)
729     {
730       buttonPanel.add(mergeResults);
731       buttonPanel.setLayout(new GridLayout(2, 1, 5, 5));
732     }
733     buttonPanel.validate();
734     validate();
735     if (viewResultsImmediatly)
736     {
737       showResultsNewFrame.doClick();
738     }
739   }
740
741   /**
742    * called when job has finished but no result objects can be passed back to
743    * user
744    */
745   public void setFinishedNoResults()
746   {
747     frame.setClosable(true);
748     buttonPanel.remove(cancel);
749     buttonPanel.validate();
750     validate();
751   }
752
753   class AnimatedPanel extends JPanel implements Runnable
754   {
755     long startTime = 0;
756
757     BufferedImage offscreen;
758
759     public void run()
760     {
761       startTime = System.currentTimeMillis();
762
763       while (currentStatus < STATE_STOPPED_OK)
764       {
765         try
766         {
767           Thread.sleep(50);
768
769           int units = (int) ((System.currentTimeMillis() - startTime)
770                   / 10f);
771           angle += units;
772           angle %= 360;
773           startTime = System.currentTimeMillis();
774
775           if (currentStatus >= STATE_STOPPED_OK)
776           {
777             angle = 0;
778           }
779
780           repaint();
781         } catch (Exception ex)
782         {
783         }
784       }
785
786       cancel.setEnabled(false);
787     }
788
789     void drawPanel()
790     {
791       if (offscreen == null || offscreen.getWidth(this) != getWidth()
792               || offscreen.getHeight(this) != getHeight())
793       {
794         offscreen = new BufferedImage(getWidth(), getHeight(),
795                 BufferedImage.TYPE_INT_ARGB);
796       }
797
798       Graphics2D g = (Graphics2D) offscreen.getGraphics();
799
800       g.setColor(Color.white);
801       g.fillRect(0, 0, getWidth(), getHeight());
802
803       g.setFont(new Font("Arial", Font.BOLD, 12));
804       g.setColor(Color.black);
805
806       switch (currentStatus)
807       {
808       case STATE_QUEUING:
809         g.drawString(
810                 title.concat(" - ").concat(
811                         MessageManager.getString("label.state_queueing")),
812                 60, 30);
813
814         break;
815
816       case STATE_RUNNING:
817         g.drawString(
818                 title.concat(" - ").concat(
819                         MessageManager.getString("label.state_running")),
820                 60, 30);
821
822         break;
823
824       case STATE_STOPPED_OK:
825         g.drawString(
826                 title.concat(" - ").concat(
827                         MessageManager.getString("label.state_completed")),
828                 60, 30);
829
830         break;
831
832       case STATE_CANCELLED_OK:
833         g.drawString(
834                 title.concat(" - ")
835                         .concat(MessageManager
836                                 .getString("label.state_job_cancelled")),
837                 60, 30);
838
839         break;
840
841       case STATE_STOPPED_ERROR:
842         g.drawString(
843                 title.concat(" - ").concat(
844                         MessageManager.getString("label.state_job_error")),
845                 60, 30);
846
847         break;
848
849       case STATE_STOPPED_SERVERERROR:
850         g.drawString(
851                 title.concat(" - ")
852                         .concat(MessageManager
853                                 .getString("label.server_error_try_later")),
854                 60, 30);
855
856         break;
857       }
858
859       if (image != null)
860       {
861         int x = image.getWidth(this) / 2, y = image.getHeight(this) / 2;
862         g.rotate(Math.toRadians(angle), 10 + x, 10 + y);
863         g.drawImage(image, 10, 10, this);
864         g.rotate(-Math.toRadians(angle), 10 + x, 10 + y);
865       }
866     }
867
868     public void paintComponent(Graphics g1)
869     {
870       drawPanel();
871
872       g1.drawImage(offscreen, 0, 0, this);
873     }
874   }
875
876   boolean renderAsHtml = false;
877
878   public void setRenderAsHtml(boolean b)
879   {
880     renderAsHtml = b;
881   }
882
883   public void hyperlinkUpdate(HyperlinkEvent e)
884   {
885     Desktop.hyperlinkUpdate(e);
886   }
887
888   /*
889    * (non-Javadoc)
890    * 
891    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
892    */
893   @Override
894   public void setProgressBar(String message, long id)
895   {
896     progressBar.setProgressBar(message, id);
897   }
898
899   @Override
900   public void registerHandler(final long id,
901           final IProgressIndicatorHandler handler)
902   {
903     progressBar.registerHandler(id, handler);
904   }
905
906   /**
907    * 
908    * @return true if any progress bars are still active
909    */
910   @Override
911   public boolean operationInProgress()
912   {
913     return progressBar.operationInProgress();
914   }
915 }