JAL-1583 refactored addInternalFrame to allow webservice job frame to be added but...
[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    */
240   public WebserviceInfo(String title, String info, boolean makeVisible)
241   {
242     init(title, info, 520, 500, makeVisible);
243   }
244
245   /**
246    * Creates a new WebserviceInfo object.
247    * 
248    * @param title
249    *          DOCUMENT ME!
250    * @param info
251    *          DOCUMENT ME!
252    * @param width
253    *          DOCUMENT ME!
254    * @param height
255    *          DOCUMENT ME!
256    */
257   public WebserviceInfo(String title, String info, int width, int height,
258           boolean makeVisible)
259   {
260     init(title, info, width, height, makeVisible);
261   }
262
263   /**
264    * DOCUMENT ME!
265    * 
266    * @return DOCUMENT ME!
267    */
268   public jalview.ws.WSClientI getthisService()
269   {
270     return thisService;
271   }
272
273   /**
274    * Update state of GUI based on client capabilities (like whether the job is
275    * cancellable, whether the 'merge results' button is shown.
276    * 
277    * @param newservice
278    *          service client to query for capabilities
279    */
280   public void setthisService(jalview.ws.WSClientI newservice)
281   {
282     thisService = newservice;
283     serviceIsCancellable = newservice.isCancellable();
284     frame.setClosable(!serviceIsCancellable);
285     serviceCanMergeResults = newservice.canMergeResults();
286     rebuildButtonPanel();
287   }
288
289   private void rebuildButtonPanel()
290   {
291     if (buttonPanel != null)
292     {
293       buttonPanel.removeAll();
294       if (serviceIsCancellable)
295       {
296         buttonPanel.add(cancel);
297         frame.setClosable(false);
298       }
299       else
300       {
301         frame.setClosable(true);
302       }
303     }
304   }
305
306   /**
307    * DOCUMENT ME!
308    * 
309    * @param title
310    *          DOCUMENT ME!
311    * @param info
312    *          DOCUMENT ME!
313    * @param width
314    *          DOCUMENT ME!
315    * @param height
316    *          DOCUMENT ME!
317    */
318   void init(String title, String info, int width, int height,
319           boolean makeVisible)
320   {
321     frame = new JInternalFrame();
322     frame.setContentPane(this);
323     Desktop.addInternalFrame(frame, title, makeVisible, width, height);
324     frame.setClosable(false);
325
326     this.title = title;
327     setInfoText(info);
328
329     java.net.URL url = getClass().getResource(
330             "/images/Jalview_Logo_small.png");
331     image = java.awt.Toolkit.getDefaultToolkit().createImage(url);
332
333     MediaTracker mt = new MediaTracker(this);
334     mt.addImage(image, 0);
335
336     try
337     {
338       mt.waitForID(0);
339     } catch (Exception ex)
340     {
341     }
342
343     AnimatedPanel ap = new AnimatedPanel();
344     titlePanel.add(ap, BorderLayout.CENTER);
345
346     Thread thread = new Thread(ap);
347     thread.start();
348     final WebserviceInfo thisinfo = this;
349     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
350     {
351       public void internalFrameClosed(
352               javax.swing.event.InternalFrameEvent evt)
353       {
354         // System.out.println("Shutting down webservice client");
355         WSClientI service = thisinfo.getthisService();
356         if (service != null && service.isCancellable())
357         {
358           service.cancelJob();
359         }
360       };
361     });
362     frame.validate();
363
364   }
365
366   /**
367    * DOCUMENT ME!
368    * 
369    * @param status
370    *          integer status from state constants
371    */
372   public void setStatus(int status)
373   {
374     currentStatus = status;
375   }
376
377   /**
378    * subjob status indicator
379    * 
380    * @param jobpane
381    * @param status
382    */
383   public void setStatus(int jobpane, int status)
384   {
385     if (jobpane < 0 || jobpane >= jobPanes.size())
386     {
387       throw new Error(MessageManager.formatMessage("error.setstatus_called_non_existent_job_pane", new String[]{Integer.valueOf(jobpane).toString()}));
388     }
389     switch (status)
390     {
391     case STATE_QUEUING:
392       setProgressName(jobpane + " - QUEUED", jobpane);
393       break;
394     case STATE_RUNNING:
395       setProgressName(jobpane + " - RUNNING", jobpane);
396       break;
397     case STATE_STOPPED_OK:
398       setProgressName(jobpane + " - FINISHED", jobpane);
399       break;
400     case STATE_CANCELLED_OK:
401       setProgressName(jobpane + " - CANCELLED", jobpane);
402       break;
403     case STATE_STOPPED_ERROR:
404       setProgressName(jobpane + " - BROKEN", jobpane);
405       break;
406     case STATE_STOPPED_SERVERERROR:
407       setProgressName(jobpane + " - ALERT", jobpane);
408       break;
409     default:
410       setProgressName(jobpane + " - UNKNOWN STATE", jobpane);
411     }
412   }
413
414   /**
415    * DOCUMENT ME!
416    * 
417    * @return DOCUMENT ME!
418    */
419   public String getInfoText()
420   {
421     return infoText.getText();
422   }
423
424   /**
425    * DOCUMENT ME!
426    * 
427    * @param text
428    *          DOCUMENT ME!
429    */
430   public void setInfoText(String text)
431   {
432     infoText.setText(text);
433   }
434
435   /**
436    * DOCUMENT ME!
437    * 
438    * @param text
439    *          DOCUMENT ME!
440    */
441   public void appendInfoText(String text)
442   {
443     infoText.append(text);
444   }
445
446   /**
447    * DOCUMENT ME!
448    * 
449    * @return DOCUMENT ME!
450    */
451   public String getProgressText(int which)
452   {
453     if (jobPanes == null)
454     {
455       addJobPane();
456     }
457     if (renderAsHtml)
458     {
459       return ((JEditorPane) ((JScrollPane) jobPanes.get(which))
460               .getViewport().getComponent(0)).getText();
461     }
462     else
463     {
464       return ((JTextArea) ((JScrollPane) jobPanes.get(which)).getViewport()
465               .getComponent(0)).getText();
466     }
467   }
468
469   /**
470    * DOCUMENT ME!
471    * 
472    * @param text
473    *          DOCUMENT ME!
474    */
475   public void setProgressText(int which, String text)
476   {
477     if (jobPanes == null)
478     {
479       addJobPane();
480     }
481     if (renderAsHtml)
482     {
483       ((JEditorPane) ((JScrollPane) jobPanes.get(which)).getViewport()
484               .getComponent(0)).setText(ensureHtmlTagged(text));
485     }
486     else
487     {
488       ((JTextArea) ((JScrollPane) jobPanes.get(which)).getViewport()
489               .getComponent(0)).setText(text);
490     }
491   }
492
493   /**
494    * extract content from &lt;body&gt; content &lt;/body&gt;
495    * 
496    * @param text
497    * @param leaveFirst
498    *          - set to leave the initial html tag intact
499    * @param leaveLast
500    *          - set to leave the final html tag intact
501    * @return
502    */
503   private String getHtmlFragment(String text, boolean leaveFirst,
504           boolean leaveLast)
505   {
506     if (text == null)
507     {
508       return null;
509     }
510     String lowertxt = text.toLowerCase();
511     int htmlpos = leaveFirst ? -1 : lowertxt.indexOf("<body");
512
513     int htmlend = leaveLast ? -1 : lowertxt.indexOf("</body");
514     int htmlpose = lowertxt.indexOf(">", htmlpos), htmlende = lowertxt
515             .indexOf(">", htmlend);
516     if (htmlend == -1 && htmlpos == -1)
517     {
518       return text;
519     }
520     if (htmlend > -1)
521     {
522       return text.substring((htmlpos == -1 ? 0 : htmlpose + 1), htmlend);
523     }
524     return text.substring(htmlpos == -1 ? 0 : htmlpose + 1);
525   }
526
527   /**
528    * very simple routine for adding/ensuring html tags are present in text.
529    * 
530    * @param text
531    * @return properly html tag enclosed text
532    */
533   private String ensureHtmlTagged(String text)
534   {
535     if (text == null)
536     {
537       return "";
538     }
539     String lowertxt = text.toLowerCase();
540     int htmlpos = lowertxt.indexOf("<body");
541     int htmlend = lowertxt.indexOf("</body");
542     int doctype = lowertxt.indexOf("<!doctype");
543     int xmltype = lowertxt.indexOf("<?xml");
544     if (htmlend == -1)
545     {
546       text = text + "</body></html>";
547     }
548     if (htmlpos > -1)
549     {
550       if ((doctype > -1 && htmlpos > doctype)
551               || (xmltype > -1 && htmlpos > xmltype))
552       {
553         text = "<html><head></head><body>\n" + text.substring(htmlpos - 1);
554       }
555     }
556     else
557     {
558       text = "<html><head></head><body>\n" + text;
559     }
560     if (text.indexOf("<meta") > -1)
561     {
562       System.err.println("HTML COntent: \n" + text
563               + "<< END HTML CONTENT\n");
564
565     }
566     return text;
567   }
568
569   /**
570    * DOCUMENT ME!
571    * 
572    * @param text
573    *          DOCUMENT ME!
574    */
575   public void appendProgressText(int which, String text)
576   {
577     if (jobPanes == null)
578     {
579       addJobPane();
580     }
581     if (renderAsHtml)
582     {
583       String txt = getHtmlFragment(
584               ((JEditorPane) ((JScrollPane) jobPanes.get(which))
585                       .getViewport().getComponent(0)).getText(), true,
586               false);
587       ((JEditorPane) ((JScrollPane) jobPanes.get(which)).getViewport()
588               .getComponent(0)).setText(ensureHtmlTagged(txt
589               + getHtmlFragment(text, false, true)));
590     }
591     else
592     {
593       ((JTextArea) ((JScrollPane) jobPanes.get(which)).getViewport()
594               .getComponent(0)).append(text);
595     }
596   }
597
598   /**
599    * setProgressText(0, text)
600    */
601   public void setProgressText(String text)
602   {
603     setProgressText(0, text);
604   }
605
606   /**
607    * appendProgressText(0, text)
608    */
609   public void appendProgressText(String text)
610   {
611     appendProgressText(0, text);
612   }
613
614   /**
615    * getProgressText(0)
616    */
617   public String getProgressText()
618   {
619     return getProgressText(0);
620   }
621
622   /**
623    * get the tab title for a subjob
624    * 
625    * @param which
626    *          int
627    * @return String
628    */
629   public String getProgressName(int which)
630   {
631     if (jobPanes == null)
632     {
633       addJobPane();
634     }
635     if (subjobs != null)
636     {
637       return subjobs.getTitleAt(which);
638     }
639     else
640     {
641       return ((JScrollPane) jobPanes.get(which)).getViewport()
642               .getComponent(0).getName();
643     }
644   }
645
646   /**
647    * set the tab title for a subjob
648    * 
649    * @param name
650    *          String
651    * @param which
652    *          int
653    */
654   public void setProgressName(String name, int which)
655   {
656     if (subjobs != null)
657     {
658       subjobs.setTitleAt(which, name);
659       subjobs.revalidate();
660       subjobs.repaint();
661     }
662     JScrollPane c = (JScrollPane) jobPanes.get(which);
663     c.getViewport().getComponent(0).setName(name);
664     c.repaint();
665   }
666
667   /**
668    * Gui action for cancelling the current job, if possible.
669    * 
670    * @param e
671    *          DOCUMENT ME!
672    */
673   protected void cancel_actionPerformed(ActionEvent e)
674   {
675     if (!serviceIsCancellable)
676     {
677       // JBPNote : TODO: We should REALLY just tell the WSClientI to cancel
678       // anyhow - it has to stop threads and clean up
679       // JBPNote : TODO: Instead of a warning, we should have an optional 'Are
680       // you sure?' prompt
681       warnUser(MessageManager.getString("warn.job_cannot_be_cancelled_close_window"),
682               MessageManager.getString("action.cancel_job"));
683     }
684     else
685     {
686       thisService.cancelJob();
687     }
688     frame.setClosable(true);
689   }
690
691   /**
692    * Spawns a thread that pops up a warning dialog box with the given message
693    * and title.
694    * 
695    * @param message
696    * @param title
697    */
698   public void warnUser(final String message, final String title)
699   {
700     javax.swing.SwingUtilities.invokeLater(new Runnable()
701     {
702       public void run()
703       {
704         JOptionPane.showInternalMessageDialog(Desktop.desktop, message,
705                 title, JOptionPane.WARNING_MESSAGE);
706
707       }
708     });
709   }
710
711   /**
712    * Set up GUI for user to get at results - and possibly automatically display
713    * them if viewResultsImmediatly is set.
714    */
715   public void setResultsReady()
716   {
717     frame.setClosable(true);
718     buttonPanel.remove(cancel);
719     buttonPanel.add(showResultsNewFrame);
720     if (serviceCanMergeResults)
721     {
722       buttonPanel.add(mergeResults);
723       buttonPanel.setLayout(new GridLayout(2, 1, 5, 5));
724     }
725     buttonPanel.validate();
726     validate();
727     if (viewResultsImmediatly)
728     {
729       showResultsNewFrame.doClick();
730     }
731   }
732
733   /**
734    * called when job has finished but no result objects can be passed back to
735    * user
736    */
737   public void setFinishedNoResults()
738   {
739     frame.setClosable(true);
740     buttonPanel.remove(cancel);
741     buttonPanel.validate();
742     validate();
743   }
744
745   class AnimatedPanel extends JPanel implements Runnable
746   {
747     long startTime = 0;
748
749     BufferedImage offscreen;
750
751     public void run()
752     {
753       startTime = System.currentTimeMillis();
754
755       while (currentStatus < STATE_STOPPED_OK)
756       {
757         try
758         {
759           Thread.sleep(50);
760
761           int units = (int) ((System.currentTimeMillis() - startTime) / 10f);
762           angle += units;
763           angle %= 360;
764           startTime = System.currentTimeMillis();
765
766           if (currentStatus >= STATE_STOPPED_OK)
767           {
768             angle = 0;
769           }
770
771           repaint();
772         } catch (Exception ex)
773         {
774         }
775       }
776
777       cancel.setEnabled(false);
778     }
779
780     void drawPanel()
781     {
782       if (offscreen == null || offscreen.getWidth(this) != getWidth()
783               || offscreen.getHeight(this) != getHeight())
784       {
785         offscreen = new BufferedImage(getWidth(), getHeight(),
786                 BufferedImage.TYPE_INT_ARGB);
787       }
788
789       Graphics2D g = (Graphics2D) offscreen.getGraphics();
790
791       g.setColor(Color.white);
792       g.fillRect(0, 0, getWidth(), getHeight());
793
794       g.setFont(new Font("Arial", Font.BOLD, 12));
795       g.setColor(Color.black);
796
797       switch (currentStatus)
798       {
799       case STATE_QUEUING:
800         g.drawString(
801                 title.concat(" - ").concat(
802                         MessageManager.getString("label.state_queueing")),
803                 60, 30);
804
805         break;
806
807       case STATE_RUNNING:
808         g.drawString(
809                 title.concat(" - ").concat(
810                         MessageManager.getString("label.state_running")),
811                 60, 30);
812
813         break;
814
815       case STATE_STOPPED_OK:
816         g.drawString(
817                 title.concat(" - ").concat(
818                         MessageManager.getString("label.state_completed")),
819                 60, 30);
820
821         break;
822
823       case STATE_CANCELLED_OK:
824         g.drawString(
825                 title.concat(" - ").concat(
826                         MessageManager
827                                 .getString("label.state_job_cancelled")),
828                 60, 30);
829
830         break;
831
832       case STATE_STOPPED_ERROR:
833         g.drawString(
834                 title.concat(" - ").concat(
835                         MessageManager.getString("label.state_job_error")),
836                 60, 30);
837
838         break;
839
840       case STATE_STOPPED_SERVERERROR:
841         g.drawString(
842                 title.concat(" - ").concat(
843                         MessageManager
844                                 .getString("label.server_error_try_later")),
845                 60, 30);
846
847         break;
848       }
849
850       if (image != null)
851       {
852         int x = image.getWidth(this) / 2, y = image.getHeight(this) / 2;
853         g.rotate(Math.toRadians(angle), 10 + x, 10 + y);
854         g.drawImage(image, 10, 10, this);
855         g.rotate(-Math.toRadians(angle), 10 + x, 10 + y);
856       }
857     }
858
859     public void paintComponent(Graphics g1)
860     {
861       drawPanel();
862
863       g1.drawImage(offscreen, 0, 0, this);
864     }
865   }
866
867   boolean renderAsHtml = false;
868
869   public void setRenderAsHtml(boolean b)
870   {
871     renderAsHtml = b;
872   }
873
874   public void hyperlinkUpdate(HyperlinkEvent e)
875   {
876     Desktop.hyperlinkUpdate(e);
877   }
878
879   // methods for implementing IProgressIndicator
880   // need to refactor to a reusable stub class
881   Hashtable progressBars, progressBarHandlers;
882
883   /*
884    * (non-Javadoc)
885    * 
886    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
887    */
888   @Override
889   public void setProgressBar(String message, long id)
890   {
891     if (progressBars == null)
892     {
893       progressBars = new Hashtable();
894       progressBarHandlers = new Hashtable();
895     }
896
897     JPanel progressPanel;
898     Long lId = new Long(id);
899     GridLayout layout = (GridLayout) statusPanel.getLayout();
900     if (progressBars.get(lId) != null)
901     {
902       progressPanel = (JPanel) progressBars.get(new Long(id));
903       statusPanel.remove(progressPanel);
904       progressBars.remove(lId);
905       progressPanel = null;
906       if (message != null)
907       {
908         statusBar.setText(message);
909       }
910       if (progressBarHandlers.contains(lId))
911       {
912         progressBarHandlers.remove(lId);
913       }
914       layout.setRows(layout.getRows() - 1);
915     }
916     else
917     {
918       progressPanel = new JPanel(new BorderLayout(10, 5));
919
920       JProgressBar progressBar = new JProgressBar();
921       progressBar.setIndeterminate(true);
922
923       progressPanel.add(new JLabel(message), BorderLayout.WEST);
924       progressPanel.add(progressBar, BorderLayout.CENTER);
925
926       layout.setRows(layout.getRows() + 1);
927       statusPanel.add(progressPanel);
928
929       progressBars.put(lId, progressPanel);
930     }
931     // update GUI
932     // setMenusForViewport();
933     validate();
934   }
935
936   @Override
937   public void registerHandler(final long id,
938           final IProgressIndicatorHandler handler)
939   {
940     if (progressBarHandlers == null || !progressBars.contains(new Long(id)))
941     {
942       throw new Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
943     }
944     progressBarHandlers.put(new Long(id), handler);
945     final JPanel progressPanel = (JPanel) progressBars.get(new Long(id));
946     if (handler.canCancel())
947     {
948       JButton cancel = new JButton(
949               MessageManager.getString("action.cancel"));
950       final IProgressIndicator us = this;
951       cancel.addActionListener(new ActionListener()
952       {
953
954         @Override
955         public void actionPerformed(ActionEvent e)
956         {
957           handler.cancelActivity(id);
958           us.setProgressBar(MessageManager.formatMessage("label.cancelled_params", new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
959         }
960       });
961       progressPanel.add(cancel, BorderLayout.EAST);
962     }
963   }
964
965   /**
966    * 
967    * @return true if any progress bars are still active
968    */
969   @Override
970   public boolean operationInProgress()
971   {
972     if (progressBars != null && progressBars.size() > 0)
973     {
974       return true;
975     }
976     return false;
977   }
978 }