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