properly report invalid jobs (server doesn't support service, seuqence set too big...
[jalview.git] / src / jalview / gui / WebserviceInfo.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.event.*;
24 import java.awt.image.*;
25 import javax.swing.*;
26
27 import jalview.jbgui.*;
28 import jalview.ws.WSClientI;
29
30 /**
31  * Base class for web service client thread and gui
32  * 
33  * @author $author$
34  * @version $Revision$
35  */
36 public class WebserviceInfo extends GWebserviceInfo
37 {
38
39   /** Job is Queued */
40   public static final int STATE_QUEUING = 0;
41
42   /** Job is Running */
43   public static final int STATE_RUNNING = 1;
44
45   /** Job has finished with no errors */
46   public static final int STATE_STOPPED_OK = 2;
47
48   /** Job has been cancelled with no errors */
49   public static final int STATE_CANCELLED_OK = 3;
50
51   /** job has stopped because of some error */
52   public static final int STATE_STOPPED_ERROR = 4;
53
54   /** job has failed because of some unavoidable service interruption */
55   public static final int STATE_STOPPED_SERVERERROR = 5;
56
57   int currentStatus = STATE_QUEUING;
58
59   Image image;
60
61   int angle = 0;
62
63   String title = "";
64
65   jalview.ws.WSClientI thisService;
66
67   boolean serviceIsCancellable;
68
69   JInternalFrame frame;
70
71   JTabbedPane subjobs = null;
72
73   java.util.Vector jobPanes = null;
74
75   private boolean serviceCanMergeResults = false;
76
77   private boolean viewResultsImmediatly = true;
78
79   // tabbed or not
80   public synchronized int addJobPane()
81   {
82     JScrollPane jobpane = new JScrollPane();
83     JTextArea progressText = new JTextArea();
84     progressText.setFont(new java.awt.Font("Verdana", 0, 10));
85     progressText.setBorder(null);
86     progressText.setEditable(false);
87     progressText.setText("WS Job");
88     progressText.setLineWrap(true);
89     progressText.setWrapStyleWord(true);
90     jobpane.setName("JobPane");
91     jobpane.getViewport().add(progressText, null);
92     jobpane.setBorder(null);
93     if (jobPanes == null)
94     {
95       jobPanes = new Vector();
96     }
97     int newpane = jobPanes.size();
98     jobPanes.add(jobpane);
99
100     if (newpane == 0)
101     {
102       this.add(jobpane, BorderLayout.CENTER);
103     }
104     else
105     {
106       if (newpane == 1)
107       {
108         // revert to a tabbed pane.
109         JScrollPane firstpane;
110         this.remove(firstpane = (JScrollPane) jobPanes.get(0));
111         subjobs = new JTabbedPane();
112         this.add(subjobs, BorderLayout.CENTER);
113         subjobs.add(firstpane);
114         subjobs.setTitleAt(0, firstpane.getName());
115       }
116       subjobs.add(jobpane);
117     }
118     return newpane; // index for accessor methods below
119   }
120
121   /**
122    * Creates a new WebserviceInfo object.
123    * 
124    * @param title
125    *          short name and job type
126    * @param info
127    *          reference or other human readable description
128    */
129   public WebserviceInfo(String title, String info)
130   {
131     init(title, info, 520, 500);
132   }
133
134   /**
135    * Creates a new WebserviceInfo object.
136    * 
137    * @param title
138    *          DOCUMENT ME!
139    * @param info
140    *          DOCUMENT ME!
141    * @param width
142    *          DOCUMENT ME!
143    * @param height
144    *          DOCUMENT ME!
145    */
146   public WebserviceInfo(String title, String info, int width, int height)
147   {
148     init(title, info, width, height);
149   }
150
151   /**
152    * DOCUMENT ME!
153    * 
154    * @return DOCUMENT ME!
155    */
156   public jalview.ws.WSClientI getthisService()
157   {
158     return thisService;
159   }
160
161   /**
162    * Update state of GUI based on client capabilities (like whether the job is
163    * cancellable, whether the 'merge results' button is shown.
164    * 
165    * @param newservice
166    *          service client to query for capabilities
167    */
168   public void setthisService(jalview.ws.WSClientI newservice)
169   {
170     thisService = newservice;
171     serviceIsCancellable = newservice.isCancellable();
172     frame.setClosable(!serviceIsCancellable);
173     serviceCanMergeResults = newservice.canMergeResults();
174     rebuildButtonPanel();
175   }
176
177   private void rebuildButtonPanel()
178   {
179     if (buttonPanel != null)
180     {
181       buttonPanel.removeAll();
182       if (serviceIsCancellable)
183       {
184         buttonPanel.add(cancel);
185         frame.setClosable(false);
186       }
187       else
188       {
189         frame.setClosable(true);
190       }
191     }
192   }
193
194   /**
195    * DOCUMENT ME!
196    * 
197    * @param title
198    *          DOCUMENT ME!
199    * @param info
200    *          DOCUMENT ME!
201    * @param width
202    *          DOCUMENT ME!
203    * @param height
204    *          DOCUMENT ME!
205    */
206   void init(String title, String info, int width, int height)
207   {
208     frame = new JInternalFrame();
209     frame.setContentPane(this);
210     Desktop.addInternalFrame(frame, title, width, height);
211     frame.setClosable(false);
212
213     this.title = title;
214     setInfoText(info);
215
216     java.net.URL url = getClass().getResource("/images/logo.gif");
217     image = java.awt.Toolkit.getDefaultToolkit().createImage(url);
218
219     MediaTracker mt = new MediaTracker(this);
220     mt.addImage(image, 0);
221
222     try
223     {
224       mt.waitForID(0);
225     } catch (Exception ex)
226     {
227     }
228
229     AnimatedPanel ap = new AnimatedPanel();
230     titlePanel.add(ap, BorderLayout.CENTER);
231
232     Thread thread = new Thread(ap);
233     thread.start();
234     final WebserviceInfo thisinfo = this;
235     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
236     {
237       public void internalFrameClosed(
238               javax.swing.event.InternalFrameEvent evt)
239       {
240         // System.out.println("Shutting down webservice client");
241         WSClientI service = thisinfo.getthisService();
242         if (service != null && service.isCancellable())
243         {
244           service.cancelJob();
245         }
246       };
247     });
248
249   }
250
251   /**
252    * DOCUMENT ME!
253    * 
254    * @param status
255    *          integer status from state constants
256    */
257   public void setStatus(int status)
258   {
259     currentStatus = status;
260   }
261
262   /**
263    * subjob status indicator
264    * 
265    * @param jobpane
266    * @param status
267    */
268   public void setStatus(int jobpane, int status)
269   {
270     if (jobpane < 0 || jobpane >= jobPanes.size())
271     {
272       throw new Error("setStatus called for non-existent job pane."
273               + jobpane);
274     }
275     switch (status)
276     {
277     case STATE_QUEUING:
278       setProgressName(jobpane + " - QUEUED", jobpane);
279       break;
280     case STATE_RUNNING:
281       setProgressName(jobpane + " - RUNNING", jobpane);
282       break;
283     case STATE_STOPPED_OK:
284       setProgressName(jobpane + " - FINISHED", jobpane);
285       break;
286     case STATE_CANCELLED_OK:
287       setProgressName(jobpane + " - CANCELLED", jobpane);
288       break;
289     case STATE_STOPPED_ERROR:
290       setProgressName(jobpane + " - BROKEN", jobpane);
291       break;
292     case STATE_STOPPED_SERVERERROR:
293       setProgressName(jobpane + " - ALERT", jobpane);
294       break;
295     default:
296       setProgressName(jobpane + " - UNKNOWN STATE", jobpane);
297     }
298   }
299
300   /**
301    * DOCUMENT ME!
302    * 
303    * @return DOCUMENT ME!
304    */
305   public String getInfoText()
306   {
307     return infoText.getText();
308   }
309
310   /**
311    * DOCUMENT ME!
312    * 
313    * @param text
314    *          DOCUMENT ME!
315    */
316   public void setInfoText(String text)
317   {
318     infoText.setText(text);
319   }
320
321   /**
322    * DOCUMENT ME!
323    * 
324    * @param text
325    *          DOCUMENT ME!
326    */
327   public void appendInfoText(String text)
328   {
329     infoText.append(text);
330   }
331
332   /**
333    * DOCUMENT ME!
334    * 
335    * @return DOCUMENT ME!
336    */
337   public String getProgressText(int which)
338   {
339     if (jobPanes == null)
340     {
341       addJobPane();
342     }
343     return ((JTextArea) ((JScrollPane) jobPanes.get(which)).getViewport()
344             .getComponent(0)).getText();
345   }
346
347   /**
348    * DOCUMENT ME!
349    * 
350    * @param text
351    *          DOCUMENT ME!
352    */
353   public void setProgressText(int which, String text)
354   {
355     if (jobPanes == null)
356     {
357       addJobPane();
358     }
359     ((JTextArea) ((JScrollPane) jobPanes.get(which)).getViewport()
360             .getComponent(0)).setText(text);
361   }
362
363   /**
364    * DOCUMENT ME!
365    * 
366    * @param text
367    *          DOCUMENT ME!
368    */
369   public void appendProgressText(int which, String text)
370   {
371     if (jobPanes == null)
372     {
373       addJobPane();
374     }
375     ((JTextArea) ((JScrollPane) jobPanes.get(which)).getViewport()
376             .getComponent(0)).append(text);
377   }
378
379   /**
380    * setProgressText(0, text)
381    */
382   public void setProgressText(String text)
383   {
384     setProgressText(0, text);
385   }
386
387   /**
388    * appendProgressText(0, text)
389    */
390   public void appendProgressText(String text)
391   {
392     appendProgressText(0, text);
393   }
394
395   /**
396    * getProgressText(0)
397    */
398   public String getProgressText()
399   {
400     return getProgressText(0);
401   }
402
403   /**
404    * get the tab title for a subjob
405    * 
406    * @param which
407    *          int
408    * @return String
409    */
410   public String getProgressName(int which)
411   {
412     if (jobPanes == null)
413     {
414       addJobPane();
415     }
416     if (subjobs != null)
417     {
418       return subjobs.getTitleAt(which);
419     }
420     else
421     {
422       return ((JScrollPane) jobPanes.get(which)).getViewport()
423               .getComponent(0).getName();
424     }
425   }
426
427   /**
428    * set the tab title for a subjob
429    * 
430    * @param name
431    *          String
432    * @param which
433    *          int
434    */
435   public void setProgressName(String name, int which)
436   {
437     if (subjobs != null)
438     {
439       subjobs.setTitleAt(which, name);
440       subjobs.revalidate();
441       subjobs.repaint();
442     }
443     JScrollPane c = (JScrollPane) jobPanes.get(which);
444     c.getViewport().getComponent(0).setName(name);
445     c.repaint();
446   }
447
448   /**
449    * Gui action for cancelling the current job, if possible.
450    * 
451    * @param e
452    *          DOCUMENT ME!
453    */
454   protected void cancel_actionPerformed(ActionEvent e)
455   {
456     if (!serviceIsCancellable)
457     {
458       // JBPNote : TODO: We should REALLY just tell the WSClientI to cancel
459       // anyhow - it has to stop threads and clean up
460       // JBPNote : TODO: Instead of a warning, we should have an optional 'Are
461       // you sure?' prompt
462       warnUser("This job cannot be cancelled.\nJust close the window.",
463               "Cancel job");
464     }
465     else
466     {
467       thisService.cancelJob();
468     }
469     frame.setClosable(true);
470   }
471
472   /**
473    * Spawns a thread that pops up a warning dialog box with the given message
474    * and title.
475    * 
476    * @param message
477    * @param title
478    */
479   public void warnUser(final String message, final String title)
480   {
481     javax.swing.SwingUtilities.invokeLater(new Runnable()
482     {
483       public void run()
484       {
485         JOptionPane.showInternalMessageDialog(Desktop.desktop, message,
486                 title, JOptionPane.WARNING_MESSAGE);
487
488       }
489     });
490   }
491
492   /**
493    * Set up GUI for user to get at results - and possibly automatically display
494    * them if viewResultsImmediatly is set.
495    */
496   public void setResultsReady()
497   {
498     frame.setClosable(true);
499     buttonPanel.remove(cancel);
500     buttonPanel.add(showResultsNewFrame);
501     if (serviceCanMergeResults)
502     {
503       buttonPanel.add(mergeResults);
504       buttonPanel.setLayout(new GridLayout(2, 1, 5, 5));
505     }
506     buttonPanel.validate();
507     validate();
508     if (viewResultsImmediatly)
509     {
510       showResultsNewFrame.doClick();
511     }
512   }
513
514   /**
515    * called when job has finished but no result objects can be passed back to
516    * user
517    */
518   public void setFinishedNoResults()
519   {
520     frame.setClosable(true);
521     buttonPanel.remove(cancel);
522     buttonPanel.validate();
523     validate();
524   }
525
526   class AnimatedPanel extends JPanel implements Runnable
527   {
528     long startTime = 0;
529
530     BufferedImage offscreen;
531
532     public void run()
533     {
534       startTime = System.currentTimeMillis();
535
536       while (currentStatus < STATE_STOPPED_OK)
537       {
538         try
539         {
540           Thread.sleep(50);
541
542           int units = (int) ((System.currentTimeMillis() - startTime) / 10f);
543           angle += units;
544           angle %= 360;
545           startTime = System.currentTimeMillis();
546
547           if (currentStatus >= STATE_STOPPED_OK)
548           {
549             angle = 0;
550           }
551
552           repaint();
553         } catch (Exception ex)
554         {
555         }
556       }
557
558       cancel.setEnabled(false);
559     }
560
561     void drawPanel()
562     {
563       if (offscreen == null || offscreen.getWidth(this) != getWidth()
564               || offscreen.getHeight(this) != getHeight())
565       {
566         offscreen = new BufferedImage(getWidth(), getHeight(),
567                 BufferedImage.TYPE_INT_ARGB);
568       }
569
570       Graphics2D g = (Graphics2D) offscreen.getGraphics();
571
572       g.setColor(Color.white);
573       g.fillRect(0, 0, getWidth(), getHeight());
574
575       g.setFont(new Font("Arial", Font.BOLD, 12));
576       g.setColor(Color.black);
577
578       switch (currentStatus)
579       {
580       case STATE_QUEUING:
581         g.drawString(title.concat(" - queuing"), 60, 30);
582
583         break;
584
585       case STATE_RUNNING:
586         g.drawString(title.concat(" - running"), 60, 30);
587
588         break;
589
590       case STATE_STOPPED_OK:
591         g.drawString(title.concat(" - complete"), 60, 30);
592
593         break;
594
595       case STATE_CANCELLED_OK:
596         g.drawString(title.concat(" - job cancelled!"), 60, 30);
597
598         break;
599
600       case STATE_STOPPED_ERROR:
601         g.drawString(title.concat(" - job error!"), 60, 30);
602
603         break;
604
605       case STATE_STOPPED_SERVERERROR:
606         g.drawString(title.concat(" - Server Error! (try later)"), 60, 30);
607
608         break;
609       }
610
611       if (image != null)
612       {
613         g.rotate(Math.toRadians(angle), 28, 28);
614         g.drawImage(image, 10, 10, this);
615         g.rotate(-Math.toRadians(angle), 28, 28);
616       }
617     }
618
619     public void paintComponent(Graphics g1)
620     {
621       drawPanel();
622
623       g1.drawImage(offscreen, 0, 0, this);
624     }
625   }
626 }