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