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