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