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