View Javadoc

1   /*
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   * 
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   */
28  
29  package uk.ac.rdg.resc.jstyx.gridservice.client;
30  
31  import java.util.Hashtable;
32  import java.io.File;
33  import java.io.IOException;
34  
35  import javax.swing.JFrame;
36  import javax.swing.JPanel;
37  import javax.swing.JLabel;
38  import javax.swing.JScrollPane;
39  import javax.swing.JTable;
40  import javax.swing.JButton;
41  import javax.swing.JOptionPane;
42  import javax.swing.JRadioButton;
43  import javax.swing.ButtonGroup;
44  import javax.swing.BorderFactory;
45  import javax.swing.JTextField;
46  import javax.swing.JFileChooser;
47  import javax.swing.JComboBox;
48  
49  import javax.swing.table.DefaultTableModel;
50  import javax.swing.table.AbstractTableModel;
51  
52  import java.awt.event.ActionListener;
53  import java.awt.event.ActionEvent;
54  import java.awt.Dimension;
55  import java.awt.BorderLayout;
56  
57  import java.util.Vector;
58  import java.util.Iterator;
59  import java.util.Enumeration;
60  
61  import info.clearthought.layout.TableLayout;
62  
63  import org.apache.mina.common.ByteBuffer;
64  
65  import uk.ac.rdg.resc.jstyx.StyxException;
66  import uk.ac.rdg.resc.jstyx.client.CStyxFile;
67  import uk.ac.rdg.resc.jstyx.client.CStyxFileChangeListener;
68  import uk.ac.rdg.resc.jstyx.types.DirEntry;
69  import uk.ac.rdg.resc.jstyx.messages.TwriteMessage;
70  import uk.ac.rdg.resc.jstyx.messages.TreadMessage;
71  import uk.ac.rdg.resc.jstyx.gridservice.config.SGSParam;
72  
73  /***
74   * GUI for interacting with an SGS instance.  This class DOES NOT WORK at the
75   * moment!  It's based on an older version of the SGS software and has not yet
76   * been updated.
77   *
78   * @author Jon Blower
79   * $Revision: 589 $
80   * $Date: 2006-02-20 17:35:01 +0000 (Mon, 20 Feb 2006) $
81   * $Log$
82   * Revision 1.30  2006/02/20 17:35:01  jonblower
83   * Implemented correct handling of output files/streams (not fully tested yet)
84   *
85   * Revision 1.29  2005/12/09 18:41:56  jonblower
86   * Continuing to simplify client interface to SGS instances
87   *
88   * Revision 1.28  2005/12/07 17:50:01  jonblower
89   * Changed gotCommandLine() to gotArguments()
90   *
91   * Revision 1.27  2005/12/07 08:56:32  jonblower
92   * Refactoring SGS client code
93   *
94   * Revision 1.25  2005/11/10 19:49:28  jonblower
95   * Renamed SGSInstanceChangeListener to SGSInstanceClientChangeListener
96   *
97   * Revision 1.24  2005/10/18 14:08:14  jonblower
98   * Removed inputfiles from namespace
99   *
100  * Revision 1.23  2005/10/14 18:09:40  jonblower
101  * Changed getInputMethods() to getInputStreams() and added synchronous and async versions
102  *
103  * Revision 1.21  2005/09/11 19:30:40  jonblower
104  * Changed call to readAllSteeringParams() to readAllSteeringParamsAsync()
105  *
106  * Revision 1.20  2005/09/09 16:34:03  jonblower
107  * Removed LBViewer as possible stream viewer
108  *
109  * Revision 1.19  2005/09/09 14:19:35  jonblower
110  * Created populatePanel() methods in panel implementations to send messages to get panel details
111  *
112  * Revision 1.18  2005/08/12 08:08:39  jonblower
113  * Developments to support web interface
114  *
115  * Revision 1.17  2005/08/04 16:49:18  jonblower
116  * Added and edited upload() methods in CStyxFile
117  *
118  * Revision 1.15  2005/08/01 16:38:05  jonblower
119  * Implemented simple parameter handling
120  *
121  * Revision 1.14  2005/07/29 16:55:49  jonblower
122  * Implementing reading command line asynchronously
123  *
124  * Revision 1.13  2005/06/14 07:45:16  jonblower
125  * Implemented setting of params and async notification of parameter changes
126  *
127  * Revision 1.12  2005/06/13 16:46:35  jonblower
128  * Implemented setting of parameter values via the GUI
129  *
130  * Revision 1.11  2005/06/10 07:54:40  jonblower
131  * Added code to convert event-based StreamViewer to InputStream-based one
132  *
133  * Revision 1.10  2005/06/07 16:44:45  jonblower
134  * Fixed problem with caching stream reader on client side
135  *
136  * Revision 1.9  2005/05/27 17:05:07  jonblower
137  * Changes to incorporate GeneralCachingStreamReader
138  *
139  * Revision 1.8  2005/05/27 07:44:07  jonblower
140  * Continuing to implement Stream viewers
141  *
142  * Revision 1.7  2005/05/26 21:33:40  jonblower
143  * Added method for viewing streams in a window
144  *
145  * Revision 1.6  2005/05/26 16:52:06  jonblower
146  * Implemented detection and viewing of output streams
147  *
148  * Revision 1.5  2005/05/25 16:59:31  jonblower
149  * Added uploadInputFile()
150  *
151  * Revision 1.4  2005/05/20 16:28:50  jonblower
152  * Continuing to implement GUI app
153  *
154  */
155 public class SGSInstanceGUI extends JFrame implements SGSInstanceClientChangeListener
156 {
157     
158     private static final int ROW_HEIGHT = 20;
159     private static final int BORDER = 10;
160     
161     // Contains the GUI for each instance, indexed by SGSInstanceClient
162     private static final Hashtable guis = new Hashtable();
163     
164     private SGSInstanceClient client; // Class that we use to interact with the service
165     
166     private JPanel masterPanel;
167     private TableLayout panelLayout;
168     private InputPanel inputPanel; // Panel for providing input data to the SGS
169     private InputFilesPanel inputFilesPanel;  // Panel for allowing input files to be uploaded
170     private ParamsPanel paramsPanel; // Panel for setting input parameters
171     private SteeringPanel steeringPanel; // Panel for doing computational steering
172     private ControlPanel ctlPanel; // Panel for controlling the service instance
173     private ServiceDataPanel sdPanel; // Panel for showing service data
174     private OutputStreamsPanel osPanel; // Panel for interacting with output streams
175     
176     private JLabel statusBar;
177     
178     /*** Creates a new instance of SGSInstanceGUI */
179     private SGSInstanceGUI(SGSInstanceClient client)
180     {
181         // Set the title of the frame to the full URL to the file,
182         // stripping the "styx://" from the start of the URL
183         super(client.getInstanceRoot().getURL().substring(7));
184         this.setBounds(200, 100, 550, 400);
185         
186         this.client = client;
187         this.client.addChangeListener(this);
188         
189         // Put the whole GUI in a scroll pane so that all of it can always be visible
190         this.masterPanel = new JPanel();
191         this.setLayout(new BorderLayout());
192         this.add(new JScrollPane(this.masterPanel), BorderLayout.CENTER);
193         double size[][] =
194         {
195             { BORDER, TableLayout.FILL, BORDER }, // Columns
196             { BORDER, TableLayout.PREFERRED, BORDER, TableLayout.PREFERRED, BORDER,
197                   TableLayout.PREFERRED, BORDER, TableLayout.PREFERRED, BORDER,
198                   ROW_HEIGHT, BORDER, TableLayout.FILL, BORDER,
199                   TableLayout.PREFERRED, BORDER}  // Rows
200         };
201         this.panelLayout = new TableLayout(size);
202         this.masterPanel.setLayout(this.panelLayout);
203         
204         // Add the input panel
205         this.inputPanel = new InputPanel();
206         this.inputPanel.populatePanel();
207         //this.masterPanel.add(this.inputPanel, "1, 1");
208         
209         // Add the panel for uploading input files
210         this.inputFilesPanel = new InputFilesPanel();
211         this.inputFilesPanel.populatePanel();
212         //this.masterPanel.add(this.inputFilesPanel, "1, 3");
213         
214         // Add the panel for setting parameters for the SGS
215         Vector params = client.getParameters();
216         Vector paramNames = new Vector();
217         for (Iterator it = params.iterator(); it.hasNext(); )
218         {
219             SGSParam param = (SGSParam)it.next();
220             if (param.getType() == SGSParam.INPUT_FILE)
221             {
222                 // TODO Add to list of input files to be uploaded
223             }
224             else if (param.getType() == SGSParam.OUTPUT_FILE)
225             {
226                 // TODO Add to list of output files that can be downloaded
227             }
228             else
229             {
230                 // Just add the name to the list of parameters
231                 paramNames.add(param.getName());
232             }
233         }
234         this.paramsPanel = new ParamsPanel((String[])paramNames.toArray(new String[0]));
235         this.masterPanel.add(this.paramsPanel, "1, 5");
236         
237         // Add the panel for steering the SGS
238         this.steeringPanel = new SteeringPanel(client.getSteerableParameterNames());
239         this.masterPanel.add(this.steeringPanel, "1, 7");
240         
241         // Add the control panel
242         this.ctlPanel = new ControlPanel();
243         this.masterPanel.add(this.ctlPanel, "1, 9");
244         
245         // Add the service data panel
246         this.sdPanel = new ServiceDataPanel(client.getServiceDataNames());
247         this.masterPanel.add(this.sdPanel, "1, 11");
248         
249         // Add the output streams panel
250         this.osPanel = new OutputStreamsPanel();
251         this.osPanel.populatePanel();
252         //this.masterPanel.add(this.osPanel, "1, 13");
253         
254         this.statusBar = new JLabel("Status bar");
255         this.add(this.statusBar, BorderLayout.SOUTH);
256         statusBar.setBorder(BorderFactory.createLoweredBevelBorder());
257         
258         this.repaintGUI();
259     }
260     
261     public static SGSInstanceGUI getGUI(SGSInstanceClient client)
262     {
263         // Looks for the GUI in the cache, then returns it if it exists.
264         // If it does not exist then create it.
265         synchronized(guis)
266         {
267             if (guis.containsKey(client))
268             {
269                 return (SGSInstanceGUI)guis.get(client);
270             }
271             else
272             {
273                 SGSInstanceGUI gui = new SGSInstanceGUI(client);
274                 guis.put(client, gui);
275                 return gui;
276             }
277         }
278     }
279     
280     /***
281      * Called when the value of a service data element changes
282      */
283     public void gotServiceDataValue(String sdName, String newData)
284     {
285         this.sdPanel.setSDEValue(sdName, newData);
286     }
287     
288     /***
289      * Called when we have a new value for a parameter
290      * @param name Name of the parameter
291      * @param value The new value of the parameter
292      */
293     public void gotParameterValue(String name, String value)
294     {
295         this.paramsPanel.gotParameterValue(name, value);
296     }
297     
298     /***
299      * Called when we have a new set of command line arguments
300      * @param newArgs The new command line arguments
301      */
302     public void gotArguments(String newArgs)
303     {
304         this.paramsPanel.setArguments(newArgs);
305     }
306     
307     /***
308      * Called when we have a new value for a steerable parameter
309      * @param name Name of the steerable parameter
310      * @param value The new value of the parameter
311      */
312     public void gotSteerableParameterValue(String name, String value)
313     {
314         this.steeringPanel.gotSteeringParameterValue(name, value);
315     }
316     
317     /***
318      * Forces the window to be laid out and packed
319      */
320     private void repaintGUI()
321     {
322         this.panelLayout.layoutContainer(this);
323         this.pack();
324     }
325     
326     /***
327      * Called when an error occurs. Shows a dialog box with the message
328      */
329     public void error(String message)
330     {
331         JOptionPane.showMessageDialog(this, message, "Error", JOptionPane.ERROR_MESSAGE);
332     }
333     
334     /***
335      * Called when the service is started
336      */
337     public void serviceStarted()
338     {
339         // Disable the start button here?  How do we know when the service
340         // has finished so that we can enable it again?
341     }
342     
343     /***
344      * Called when the input files have been successfully uploaded.  This is our
345      * cue to start the service going
346      * @todo add arguments to this
347      */
348     public void inputFilesUploaded()
349     {
350         this.client.startServiceAsync();
351     }
352     
353     /***
354      * Called when the service is stopped before it has finished
355      */
356     public void serviceAborted() {}
357     
358     /***
359      * Panel for providing input data to the SGS
360      */
361     private class InputPanel extends JPanel implements ActionListener
362     {
363         private TableLayout layout;
364         private JTextField inputURL;
365         private JButton btnPickFile;
366         
367         public void populatePanel()
368         {
369             // Send message to find the input methods supported by this instance
370             // When the reply arrives, the setInputMethods() method of this
371             // class will be called and the GUI will be set up.  If there are
372             // no input methods this panel will not appear
373             //client.getInputStreamsAsync();
374         }
375         
376         public void setInputStreams(CStyxFile[] inputStreams)
377         {
378             // We only expect 0 or 1 input methods (either the service expects
379             // data on stdin or it doesn't).
380             if (inputStreams.length > 0)
381             {
382                 double[][] size =
383                 {
384                     { TableLayout.FILL, BORDER, TableLayout.PREFERRED }, // Columns
385                     { ROW_HEIGHT }  // Rows - will be added later when setInputMethods() is called
386                 };
387                 this.layout = new TableLayout(size);
388                 this.setLayout(this.layout);
389 
390                 this.setBorder(BorderFactory.createTitledBorder("File to stream to stdin"));
391                 
392                 this.inputURL = new JTextField();
393                 this.add(this.inputURL, "0, 0");
394                 
395                 this.btnPickFile = new JButton("Pick file");
396                 this.btnPickFile.addActionListener(this);
397                 this.add(this.btnPickFile, "2, 0");
398             }
399         }
400         
401         public void actionPerformed(ActionEvent e)
402         {
403             if (e.getSource() == this.btnPickFile)
404             {
405                 JFileChooser chooser = new JFileChooser();
406                 int returnVal = chooser.showOpenDialog(this);
407                 if (returnVal == JFileChooser.APPROVE_OPTION)
408                 {
409                     try
410                     {
411                         this.inputURL.setText(chooser.getSelectedFile().toURL().toString());
412                     }
413                     catch(java.net.MalformedURLException mue)
414                     {
415                         // This shouldn't happen - what should we do here?
416                     }
417                 }
418             }
419         }
420         
421         /***
422          * Return the input URL that has been set, or null if we haven't set
423          * a URL
424          */
425         public String getInputURL()
426         {
427             return this.inputURL == null ? null : this.inputURL.getText();
428         }
429     }
430     
431     /***
432      * Panel for allowing uploading of input files
433      */
434     private class InputFilesPanel extends JPanel implements ActionListener
435     {
436         private TableLayout layout;
437         private JButton btnAddInputFile;
438         private Vector destFileNames; // Vector of JTextFields
439         private Vector srcFileLocations; // Vector of JTextFields
440         private Vector pickFileButtons; // Vector of JButtons
441         private Vector deleteRowButtons; // Vector of JButtons
442         private int numCompulsoryFiles;
443         
444         public InputFilesPanel()
445         {
446             double[][] size = 
447             {
448                 { TableLayout.PREFERRED, BORDER, 300, BORDER, 80, BORDER, 80 }, // columns
449                 { } // rows will be added later
450             };
451             this.layout = new TableLayout(size);
452             this.setLayout(this.layout);
453             this.setBorder(BorderFactory.createTitledBorder("Input files to upload"));
454         }
455         
456         public void populatePanel()
457         {
458             // Send a message to get all the possible input files
459             //client.getInputFiles();
460         }
461         
462         private void updateGUI()
463         {
464             this.layout.layoutContainer(this);
465             repaintGUI();
466         }
467         
468         /***
469          * Called when we have found the input files required by this service
470          */
471         public void gotInputFiles(CStyxFile[] inputFiles, boolean allowOtherInputFiles)
472         {
473             this.numCompulsoryFiles = inputFiles.length;
474             this.destFileNames = new Vector();
475             this.srcFileLocations = new Vector();
476             this.pickFileButtons = new Vector();
477             this.deleteRowButtons = new Vector();
478             
479             if (allowOtherInputFiles)
480             {
481                 // create a row for the "more input files" button
482                 this.layout.insertRow(0, ROW_HEIGHT);
483                 this.layout.insertRow(1, BORDER);
484                 this.btnAddInputFile = new JButton("Add another input file");
485                 this.btnAddInputFile.addActionListener(this);
486                 this.add(btnAddInputFile, "0, 0, 2, 0, l, f");
487             }
488             
489             for (int i = 0; i < inputFiles.length; i++)
490             {
491                 this.addInputFileRow(inputFiles[i].getName(), false);
492             }
493             this.updateGUI();
494         }
495         
496         private synchronized void addInputFileRow(String name, boolean editable)
497         {
498             int rowToAdd = this.layout.getNumRow();
499             this.layout.insertRow(rowToAdd, ROW_HEIGHT);
500             this.layout.insertRow(rowToAdd + 1, BORDER);
501 
502             JTextField destFile = new JTextField(name);
503             if (!editable)
504             {
505                 destFile.setEditable(false);
506             }
507             this.destFileNames.add(destFile);
508             this.add(destFile, "0, " + rowToAdd);
509 
510             JTextField ta = new JTextField();
511             this.srcFileLocations.add(ta);
512             this.add(ta, "2, " + rowToAdd);
513             JButton btn = new JButton("Pick file");
514 
515             this.pickFileButtons.add(btn);
516             btn.addActionListener(this);
517             this.add(btn, "4, " + rowToAdd);
518             
519             /*if (deleteable)
520             {
521                 JButton delBtn = new JButton("Remove");
522                 this.deleteRowButtons.add(delBtn);
523                 delBtn.addActionListener(this);
524                 this.add(delBtn, "6, " + rowToAdd);
525             }*/
526         }
527         
528         /***
529          * Gets an array of File objects representing the source files to
530          * be uploaded
531          */
532         public synchronized File[] getSourceFiles()
533         {
534             File[] srcFiles = new File[this.srcFileLocations.size()];
535             for (int i = 0; i < this.srcFileLocations.size(); i++)
536             {
537                 JTextField tf = (JTextField)this.srcFileLocations.get(i);
538                 srcFiles[i] = new File(tf.getText());
539             }
540             return srcFiles;
541         }
542         
543         /***
544          * Gets an array of Strings representing the names of the target files
545          * on the remote server
546          */
547         public synchronized String[] getTargetFileNames()
548         {
549             String[] destNames = new String[this.destFileNames.size()];
550             for (int i = 0; i < this.destFileNames.size(); i++)
551             {
552                 JTextField tf = (JTextField)this.destFileNames.get(i);
553                 destNames[i] = tf.getText();
554             }
555             return destNames;
556         }
557         
558         /***
559          * Called when a button is pressed on the panel
560          */
561         public void actionPerformed(ActionEvent e)
562         {
563             if (e.getSource() == this.btnAddInputFile)
564             {
565                 this.addInputFileRow("", true);
566                 this.updateGUI();
567             }
568             else
569             {
570                 // See if we've clicked a "pick file button"
571                 int index = this.pickFileButtons.indexOf(e.getSource());
572                 if (index != -1)
573                 {
574                     JFileChooser chooser = new JFileChooser();
575                     int returnVal = chooser.showOpenDialog(this);
576                     if (returnVal == JFileChooser.APPROVE_OPTION)
577                     {
578                         JTextField ta = (JTextField)this.srcFileLocations.get(index);
579                         ta.setText(chooser.getSelectedFile().getPath());
580                     }
581                 }
582                 else
583                 {
584                     // We must have clicked a "remove" button
585                     // TODO: make this work
586                 }
587             }
588         }
589     }
590     
591     /***
592      * Panel for entering parameters for the SGS
593      */
594     private class ParamsPanel extends JPanel
595     {
596         private JTable table;
597         private TableLayout layout;
598         private ParamsTableModel model;
599         private JLabel argsLabel;
600         
601         public ParamsPanel(String[] paramNames)
602         {
603             this.argsLabel = new JLabel("");
604             // We won't bother displaying the panel if the SGS doesn't expect
605             // any parameters
606             if (paramNames.length > 0)
607             {
608                 double[][] size = 
609                 {
610                     { TableLayout.PREFERRED }, // columns
611                     { TableLayout.PREFERRED, BORDER, ROW_HEIGHT} // rows
612                 };
613                 this.layout = new TableLayout(size);
614                 this.setLayout(this.layout);
615                 this.setBorder(BorderFactory.createTitledBorder("Parameters"));
616 
617                 this.model = new ParamsTableModel(paramNames, false);
618                 this.table = new JTable(this.model);
619                 this.add(new JScrollPane(this.table), "0, 0");
620                 this.add(this.argsLabel, "0, 2");
621 
622                 for (int i = 0; i < paramNames.length; i++)
623                 {
624                     this.table.getModel().setValueAt(paramNames[i], i, 0);
625                 }
626 
627                 double width = this.table.getPreferredScrollableViewportSize().getWidth();
628                 double height = this.table.getRowHeight() * paramNames.length;
629                 Dimension d = new Dimension();
630                 d.setSize(width, height);
631                 this.table.setPreferredScrollableViewportSize(d);
632                 // We read all the parameter values so that we are notified when
633                 // other clients change the parameter values
634                 client.readAllParameterValuesAsync();
635             }
636             else
637             {
638                 double[][] size = 
639                 {
640                     { TableLayout.PREFERRED }, // columns
641                     { ROW_HEIGHT} // rows
642                 };
643                 this.layout = new TableLayout(size);
644                 this.setLayout(this.layout);
645                 this.setBorder(BorderFactory.createTitledBorder("Parameters"));
646                 this.add(this.argsLabel, "0, 0");
647             }
648             this.layout.layoutContainer(this);
649             repaintGUI();
650             client.getArgumentsAsync();
651         }
652         
653         public void gotParameterValue(String name, String value)
654         {
655             this.model.setParameterValue(name, value);
656         }
657         
658         public void setArguments(String newArgs)
659         {
660             this.argsLabel.setText(client.getName() + " " + newArgs);
661             this.repaint();
662         }
663     }
664         
665     /***
666      * Table model for the parameters
667      */
668     private class ParamsTableModel extends DefaultTableModel
669     {
670         private String[] paramNames;
671         private boolean steering; // True if this is for steering parameters
672 
673         public ParamsTableModel(String[] paramNames, boolean steering)
674         {
675             this.paramNames = paramNames;
676             this.steering = steering;
677 
678             // Set the column names
679             this.setColumnIdentifiers(new String[]{"Parameter", "Value"});
680 
681             // Add the row data
682             for (int i = 0; i < paramNames.length; i++)
683             {
684                 this.addRow(new String[]{"", ""});
685             }
686         }
687 
688         public void setParameterValue(String name, String value)
689         {
690             for (int i = 0; i < this.paramNames.length; i++)
691             {
692                 if (paramNames[i].equals(name))
693                 {
694                     super.setValueAt(value, i, 1);
695                     return;
696                 }
697             }
698         }
699 
700         /***
701          * We override this method so that we can see if the server allows
702          * changes to the parameter value to be made
703          */
704         public void setValueAt(Object value, int row, int col)
705         {
706             if (col == 0)
707             {
708                 // Just allow setting of parameter names
709                 super.setValueAt(value, row, col);
710             }
711             else
712             {
713                 // Set the value of the parameter: when confirmation arrives that
714                 // the setting was successful, the value will be updated in the
715                 // table.
716                 if (this.steering)
717                 {
718                     client.setSteerableParameterValueAsync(this.paramNames[row], (String)value);
719                 }
720                 else
721                 {
722                     // TODO do this properly
723                     //client.setParameterValueAsync(this.paramNames[row], (String)value);
724                 }
725             }
726         }
727 
728         /***
729          * Only the "Value" column is editable
730          */
731         public boolean isCellEditable(int row, int col)
732         {
733             // TODO: make non-editable once service has started (and if the
734             // parameters aren't steerable)
735             return (col == 1);
736         }
737     }
738     
739     /***
740      * Panel for entering steering parameters for the SGS
741      */
742     private class SteeringPanel extends JPanel
743     {
744         private JTable table;
745         private TableLayout layout;
746         private ParamsTableModel model;
747     
748         private SteeringPanel(String[] steerableNames)
749         {
750             // We won't bother displaying the panel if the SGS doesn't expect
751             // any parameters
752             if (steerableNames.length > 0)
753             {
754                 double[][] size = 
755                 {
756                     { TableLayout.PREFERRED }, // columns
757                     { TableLayout.PREFERRED } // rows
758                 };
759                 this.layout = new TableLayout(size);
760                 this.setLayout(this.layout);
761                 this.setBorder(BorderFactory.createTitledBorder("Steering"));
762 
763                 this.model = new ParamsTableModel(steerableNames, true);
764                 this.table = new JTable(this.model);
765                 this.add(new JScrollPane(this.table), "0, 0");
766 
767                 for (int i = 0; i < steerableNames.length; i++)
768                 {
769                     this.table.getModel().setValueAt(steerableNames[i], i, 0);
770                 }
771 
772                 double width = this.table.getPreferredScrollableViewportSize().getWidth();
773                 double height = this.table.getRowHeight() * steerableNames.length;
774                 Dimension d = new Dimension();
775                 d.setSize(width, height);
776                 this.table.setPreferredScrollableViewportSize(d);
777                 this.repaint();
778                 client.readAllSteerableParameterValuesAsync();
779             }
780         }
781         
782         public void gotSteeringParameterValue(String name, String value)
783         {
784             this.model.setParameterValue(name, value);
785         }
786     }
787     
788     /***
789      * Panel for displaying control buttons (start, stop)
790      */
791     private class ControlPanel extends JPanel implements ActionListener
792     {
793         private JButton btnStart;
794         private JButton btnStop;
795         
796         public ControlPanel()
797         {
798             double size[][] =
799             {
800                 { TableLayout.FILL, BORDER, TableLayout.FILL }, // Columns
801                 { TableLayout.FILL }  // Rows
802             };
803             this.setLayout(new TableLayout(size));
804             this.btnStart = new JButton("Start");
805             this.btnStop = new JButton("Stop");
806             // Add the action listeners to both buttons
807             this.btnStart.addActionListener(this);
808             this.btnStop.addActionListener(this);
809             // Add the buttons centred in the horizontal and fully-justified
810             // in the vertical
811             this.add(this.btnStart, "0, 0, c, f");
812             this.add(this.btnStop, "2, 0, c, f");
813         }
814         
815         /***
816          * Called when a button on the panel is clicked
817          */
818         public void actionPerformed(ActionEvent e)
819         {
820             if (e.getSource() == this.btnStart)
821             {
822                 client.startServiceAsync();
823             }
824             else if (e.getSource() == this.btnStop)
825             {
826                 client.stopServiceAsync();
827             }
828         }
829     }
830     
831     /***
832      * Panel for displaying service data
833      */
834     private class ServiceDataPanel extends JPanel
835     {
836         private JTable table;
837         private SDTableModel model;
838         
839         public ServiceDataPanel(String[] sdeNames)
840         {
841             if (sdeNames.length > 0)
842             {
843                 this.setBorder(BorderFactory.createTitledBorder("Service data"));
844                 
845                 this.model = new SDTableModel();
846                 this.table = new JTable(this.model);
847                 this.add(new JScrollPane(this.table));
848                 this.model.setSDENames(sdeNames);
849 
850                 // Set the dimensions of the table
851                 double width = this.table.getPreferredScrollableViewportSize().getWidth();
852                 double height = this.table.getRowHeight() * sdeNames.length;
853                 Dimension d = new Dimension();
854                 d.setSize(width, height);
855                 this.table.setPreferredScrollableViewportSize(d);
856                 client.readAllServiceDataValuesAsync();
857             }
858         }
859         
860         /***
861          * Sets the given service data element to the given value
862          */
863         public void setSDEValue(String sdeName, String value)
864         {
865             this.model.setSDEValue(sdeName, value);
866         }
867         
868         /***
869          * Table model for the service data
870          */
871         private class SDTableModel extends AbstractTableModel
872         {
873             private String[] sdeNames;
874             private String[] sdeValues;
875             
876             public void setSDENames(String[] sdeNames)
877             {
878                 this.sdeNames = sdeNames;
879                 this.sdeValues = new String[this.sdeNames.length];
880                 this.fireTableDataChanged();
881             }
882             
883             public int getRowCount()
884             {
885                 if (this.sdeNames == null)
886                 {
887                     return 0;
888                 }
889                 else
890                 {
891                     return this.sdeNames.length;
892                 }
893             }
894             
895             public int getColumnCount()
896             {
897                 return 2;
898             }
899             
900             public Object getValueAt(int row, int column)
901             {
902                 if (column == 0)
903                 {
904                     return this.sdeNames[row];
905                 }
906                 else
907                 {
908                     return this.sdeValues[row];
909                 }
910             }
911             
912             /***
913              * Set the given service data element to the given value
914              */
915             public void setSDEValue(String sdeName, String value)
916             {
917                 for (int i = 0; i < this.sdeNames.length; i++)
918                 {
919                     if (sdeName.equals(this.sdeNames[i]))
920                     {
921                         this.sdeValues[i] = value;
922                         this.fireTableRowsUpdated(i, i);
923                         break;
924                     }
925                 }
926             }
927             
928             public String getColumnName(int column)
929             {
930                 if (column == 0)
931                 {
932                     return "Service data element";
933                 }
934                 else
935                 {
936                     return "Value";
937                 }
938             }
939         }
940     }
941     
942     /***
943      * Panel for displaying data from output streams
944      */
945     private class OutputStreamsPanel extends JPanel implements ActionListener
946     {
947         private TableLayout layout;
948         private Vector streams; // CStyxFiles
949         private Vector buttons; // JButtons
950         private Vector combos;  // JComboBoxes
951         private Hashtable viewers; // Keys are Strings, values are Classes
952         
953         public OutputStreamsPanel()
954         {
955             double[][] size =
956             {
957                 { TableLayout.PREFERRED, BORDER, TableLayout.PREFERRED, BORDER,
958                      TableLayout.FILL }, // columns
959                 {} // We'll add rows later
960             };
961             this.layout = new TableLayout(size);
962             this.setLayout(this.layout);
963             this.setBorder(BorderFactory.createTitledBorder("Available output streams"));
964             this.streams = new Vector();
965             this.buttons = new Vector();
966             this.combos = new Vector();
967             // Create the hashtable of possible viewing panels
968             this.viewers = new Hashtable();
969             this.viewers.put("Text Viewer", TextStreamViewer.class);
970         }
971         
972         public void populatePanel()
973         {
974             // Send a message to get the possible output streams
975             //client.getOutputStreamsAsync();
976         }
977         
978         private JComboBox makeComboBox()
979         {
980             JComboBox combo = new JComboBox();
981             Enumeration keys = this.viewers.keys();
982             while(keys.hasMoreElements())
983             {
984                 combo.addItem(keys.nextElement());
985             }
986             combo.setSelectedIndex(0);
987             return combo;
988         }
989         
990         /***
991          * Called when we have got the possible output streams
992          */
993         public void gotOutputStreams(CStyxFile[] outputStreams)
994         {
995             for (int i = 0; i < outputStreams.length; i++)
996             {
997                 this.layout.insertRow(2 * i, ROW_HEIGHT);
998                 this.layout.insertRow((2 * i) + 1, BORDER);
999                 this.streams.add(outputStreams[i]);
1000                 this.add(new JLabel(outputStreams[i].getName()), "0, " + 2*i);
1001                 JComboBox combo = makeComboBox();
1002                 this.combos.add(combo);
1003                 this.add(combo, "2, " + 2*i);
1004                 JButton btnView = new JButton("View stream");
1005                 btnView.addActionListener(this);
1006                 this.buttons.add(btnView);
1007                 this.add(btnView, "4, " + 2*i + ", c, f");
1008             }
1009             this.layout.layoutContainer(this);
1010             repaintGUI();
1011         }
1012         
1013         public void actionPerformed(ActionEvent e)
1014         {
1015             int i = this.buttons.indexOf(e.getSource());
1016             if (i != -1)
1017             {
1018                 CStyxFile streamFile = (CStyxFile)this.streams.get(i);
1019                 // Get the CachedStreamReader for this file and start it
1020                 CachedStreamReader reader = client.getStreamReader(streamFile);
1021                 try
1022                 {
1023                     reader.start();
1024                     // find out which viewer has been selected
1025                     JComboBox combo = (JComboBox)this.combos.get(i);
1026                     Object key = combo.getSelectedItem();
1027                     Class viewerClass = (Class)this.viewers.get(key);
1028                     StreamViewer viewer = (StreamViewer)viewerClass.newInstance();
1029                     viewer.setStreamReader(reader);
1030                     viewer.start();
1031                 }
1032                 catch (IOException ioe)
1033                 {
1034                     ioe.printStackTrace(); // TODO do something better here
1035                 }
1036                 catch (InstantiationException ie)
1037                 {
1038                     ie.printStackTrace(); // TODO do something better here
1039                 }
1040                 catch (IllegalAccessException iae)
1041                 {
1042                     iae.printStackTrace();
1043                 }
1044             }
1045         }
1046     }
1047     
1048     /***
1049      * Called when all the output data have been downloaded
1050      */
1051     public void allOutputDataDownloaded() {}
1052     /***
1053      * Called when the exit code from the service is received: this signals that
1054      * the remote executable has completed.
1055      */
1056     public void gotExitCode(int exitCode) {}
1057     
1058 }
1059 
1060