Merge branch 'alpha/origin_2022_JAL-3066_Jalview_212_slivka-integration' into spike...
[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.Locale;
40 import java.util.Vector;
41
42 import javax.swing.JComponent;
43 import javax.swing.JEditorPane;
44 import javax.swing.JInternalFrame;
45 import javax.swing.JPanel;
46 import javax.swing.JScrollPane;
47 import javax.swing.JTabbedPane;
48 import javax.swing.JTextArea;
49 import javax.swing.event.HyperlinkEvent;
50 import javax.swing.event.HyperlinkListener;
51 import javax.swing.event.InternalFrameAdapter;
52 import javax.swing.event.InternalFrameEvent;
53 import javax.swing.text.html.HTMLEditorKit;
54 import javax.swing.text.html.StyleSheet;
55
56 /**
57  * Base class for web service client thread and gui TODO: create StAX parser to
58  * extract html body content reliably when preparing html formatted job statuses
59  * 
60  * @author $author$
61  * @version $Revision$
62  */
63 public class WebserviceInfo extends GWebserviceInfo
64         implements HyperlinkListener, IProgressIndicator
65 {
66
67   /** Job is Queued */
68   public static final int STATE_QUEUING = 0;
69
70   /** Job is Running */
71   public static final int STATE_RUNNING = 1;
72
73   /** Job has finished with no errors */
74   public static final int STATE_STOPPED_OK = 2;
75
76   /** Job has been cancelled with no errors */
77   public static final int STATE_CANCELLED_OK = 3;
78
79   /** job has stopped because of some error */
80   public static final int STATE_STOPPED_ERROR = 4;
81
82   /** job has failed because of some unavoidable service interruption */
83   public static final int STATE_STOPPED_SERVERERROR = 5;
84
85   int currentStatus = STATE_QUEUING;
86
87   Image image;
88
89   float angle = 0f;
90
91   String title = "";
92
93   jalview.ws.WSClientI thisService;
94
95   boolean serviceIsCancellable;
96
97   JInternalFrame frame;
98
99   private IProgressIndicator progressBar;
100
101   @Override
102   public void setVisible(boolean aFlag)
103   {
104     super.setVisible(aFlag);
105     frame.setVisible(aFlag);
106   }
107
108   JTabbedPane subjobs = null;
109
110   java.util.Vector jobPanes = null;
111
112   private boolean serviceCanMergeResults = false;
113
114   private boolean viewResultsImmediatly = true;
115
116   /**
117    * Get
118    * 
119    * @param flag
120    *          to indicate if results will be shown in a new window as soon as
121    *          they are available.
122    */
123   public boolean isViewResultsImmediatly()
124   {
125     return viewResultsImmediatly;
126   }
127
128   /**
129    * Set
130    * 
131    * @param flag
132    *          to indicate if results will be shown in a new window as soon as
133    *          they are available.
134    */
135   public void setViewResultsImmediatly(boolean viewResultsImmediatly)
136   {
137     this.viewResultsImmediatly = viewResultsImmediatly;
138   }
139
140   private StyleSheet getStyleSheet(HTMLEditorKit editorKit)
141   {
142
143     // Copied blatantly from
144     // http://www.velocityreviews.com/forums/t132265-string-into-htmldocument.html
145     StyleSheet myStyleSheet = new StyleSheet();
146
147     myStyleSheet.addStyleSheet(editorKit.getStyleSheet());
148
149     editorKit.setStyleSheet(myStyleSheet);
150
151     /*
152      * Set the style sheet rules here by reading them from the constants
153      * interface.
154      */
155     /*
156      * for (int ix=0; ix<CSS_RULES.length; ix++) {
157      * 
158      * myStyleSheet.addRule(CSS_RULES[ix]);
159      * 
160      * }
161      */
162     return myStyleSheet;
163
164   }
165
166   // tabbed or not
167   public synchronized int addJobPane()
168   {
169     JScrollPane jobpane = new JScrollPane();
170     JComponent _progressText;
171     if (renderAsHtml)
172     {
173       JEditorPane progressText = new JEditorPane("text/html", "");
174       progressText.addHyperlinkListener(this);
175       _progressText = progressText;
176       // progressText.setFont(new java.awt.Font("Verdana", 0, 10));
177       // progressText.setBorder(null);
178       progressText.setEditable(false);
179       /*
180        * HTMLEditorKit myEditorKit = new HTMLEditorKit();
181        * 
182        * StyleSheet myStyleSheet = getStyleSheet(myEditorKit);
183        * 
184        * HTMLDocument tipDocument = (HTMLDocument)
185        * (myEditorKit.createDefaultDocument());
186        * 
187        * progressText.setDocument(tipDocument);
188        */progressText.setText("<html><h1>WS Job</h1></html>");
189     }
190     else
191     {
192       JTextArea progressText = new JTextArea();
193       _progressText = progressText;
194
195       progressText.setFont(new java.awt.Font("Verdana", 0, 10));
196       progressText.setBorder(null);
197       progressText.setEditable(false);
198       progressText.setText("WS Job");
199       progressText.setLineWrap(true);
200       progressText.setWrapStyleWord(true);
201     }
202     jobpane.setName("JobPane");
203     jobpane.getViewport().add(_progressText, null);
204     jobpane.setBorder(null);
205     if (jobPanes == null)
206     {
207       jobPanes = new Vector();
208     }
209     int newpane = jobPanes.size();
210     jobPanes.add(jobpane);
211
212     if (newpane == 0)
213     {
214       this.add(jobpane, BorderLayout.CENTER);
215     }
216     else
217     {
218       if (newpane == 1)
219       {
220         // revert to a tabbed pane.
221         JScrollPane firstpane;
222         this.remove(firstpane = (JScrollPane) jobPanes.get(0));
223         subjobs = new JTabbedPane();
224         this.add(subjobs, BorderLayout.CENTER);
225         subjobs.add(firstpane);
226         subjobs.setTitleAt(0, firstpane.getName());
227       }
228       subjobs.add(jobpane);
229     }
230     return newpane; // index for accessor methods below
231   }
232
233   /**
234    * Creates a new WebserviceInfo object.
235    * 
236    * @param title
237    *          short name and job type
238    * @param info
239    *          reference or other human readable description
240    * @param makeVisible
241    *          true to display the webservices window immediatly (otherwise need
242    *          to call setVisible(true))
243    */
244   public WebserviceInfo(String title, String info, boolean makeVisible)
245   {
246     init(title, info, 520, 500, makeVisible);
247   }
248
249   /**
250    * Creates a new WebserviceInfo object.
251    * 
252    * @param title
253    *          DOCUMENT ME!
254    * @param info
255    *          DOCUMENT ME!
256    * @param width
257    *          DOCUMENT ME!
258    * @param height
259    *          DOCUMENT ME!
260    */
261   public WebserviceInfo(String title, String info, int width, int height,
262           boolean makeVisible)
263   {
264     // no references
265     init(title, info, width, height, makeVisible);
266   }
267
268   /**
269    * DOCUMENT ME!
270    * 
271    * @return DOCUMENT ME!
272    */
273   public jalview.ws.WSClientI getthisService()
274   {
275     return thisService;
276   }
277
278   /**
279    * Update state of GUI based on client capabilities (like whether the job is
280    * cancellable, whether the 'merge results' button is shown.
281    * 
282    * @param newservice
283    *          service client to query for capabilities
284    */
285   public void setthisService(jalview.ws.WSClientI newservice)
286   {
287     thisService = newservice;
288     serviceIsCancellable = newservice.isCancellable();
289     frame.setClosable(!serviceIsCancellable);
290     serviceCanMergeResults = newservice.canMergeResults();
291     rebuildButtonPanel();
292   }
293
294   private void rebuildButtonPanel()
295   {
296     if (buttonPanel != null)
297     {
298       buttonPanel.removeAll();
299       if (serviceIsCancellable)
300       {
301         buttonPanel.add(cancel);
302         frame.setClosable(false);
303       }
304       else
305       {
306         frame.setClosable(true);
307       }
308     }
309   }
310
311   /**
312    * DOCUMENT ME!
313    * 
314    * @param title
315    *          DOCUMENT ME!
316    * @param info
317    *          DOCUMENT ME!
318    * @param width
319    *          DOCUMENT ME!
320    * @param height
321    *          DOCUMENT ME!
322    */
323   void init(String title, String info, int width, int height,
324           boolean makeVisible)
325   {
326     frame = new JInternalFrame();
327     frame.setContentPane(this);
328     Desktop.addInternalFrame(frame, title, makeVisible, width, height, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_SET_MIN_SIZE_300);
329     frame.setClosable(false);
330
331     progressBar = new ProgressBar(statusPanel, statusBar);
332
333     this.title = title;
334     setInfoText(info);
335
336     image = ChannelProperties.getImage("rotatable_logo.48");
337
338     MediaTracker mt = new MediaTracker(this);
339     mt.addImage(image, 0);
340
341     try
342     {
343       mt.waitForID(0);
344     } catch (Exception ex)
345     {
346     }
347
348     AnimatedPanel ap = new AnimatedPanel();
349     ap.setPreferredSize(new Dimension(60, 60));
350     titlePanel.add(ap, BorderLayout.WEST);
351     titlePanel.add(titleText, BorderLayout.CENTER);
352     setStatus(currentStatus);
353
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(Locale.ROOT);
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(Locale.ROOT);
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 }