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