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