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