001/* A base class for Ptolemy applets.
002
003 Copyright (c) 1999-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.actor.gui;
029
030import java.awt.event.ActionEvent;
031import java.awt.event.ActionListener;
032import java.lang.reflect.Constructor;
033import java.util.Locale;
034import java.util.StringTokenizer;
035
036import javax.swing.JButton;
037import javax.swing.JPanel;
038
039import ptolemy.actor.CompositeActor;
040import ptolemy.actor.ExecutionListener;
041import ptolemy.actor.Manager;
042import ptolemy.gui.BasicJApplet;
043import ptolemy.kernel.attributes.VersionAttribute;
044import ptolemy.kernel.util.BasicModelErrorHandler;
045import ptolemy.kernel.util.IllegalActionException;
046import ptolemy.kernel.util.NamedObj;
047import ptolemy.kernel.util.Workspace;
048
049///////////////////////////////////////////////////////////////////
050//// PtolemyApplet
051
052/**
053 This class provides a convenient way to make applets out of Ptolemy II
054 models.  It assumes that the model is defined as a Java class that
055 extends NamedObj, with the classname given by the
056 <i>modelClass</i> applet parameter. If that model does not contain
057 a manager, then this class will create one for it.
058 <p>
059 This class offers a number of alternatives that control the visual
060 appearance of the applet. By default, the applet places on the screen
061 a set of control buttons that can be used to start, stop, pause, and
062 resume the model.  Below those buttons, it places the visual elements
063 of any actors in the model that implement the Placeable interface,
064 such as plotters or textual output.
065 <p>
066 The applet parameters are:
067 <ul>
068
069 <li>
070 <i>background</i>: The background color, typically given as a hex
071 number of the form "#<i>rrggbb</i>" where <i>rr</i> gives the red
072 component, <i>gg</i> gives the green component, and <i>bb</i> gives
073 the blue component.
074
075 <li>
076 <i>controls</i>:
077 This gives a comma-separated list
078 of any subset of the words "buttons", "topParameters", and
079 "directorParameters" (case insensitive), or the word "none".
080 If this parameter is not given, then it is equivalent to
081 giving "buttons", and only the control buttons mentioned above
082 will be displayed.  If the parameter is given, and its value is "none",
083 then no controls are placed on the screen.  If the word "topParameters"
084 is included in the comma-separated list, then controls for the
085 top-level parameters of the model are placed on the screen, below
086 the buttons.  If the word "directorParameters" is included,
087 then controls for the director parameters are also included.
088
089 <li>
090 <i>modelClass</i>: The fully qualified class name of a Java class
091 that extends NamedObj.  This class defines the model.
092
093 <li>
094 <i>orientation</i>: This can have value "horizontal", "vertical", or
095 "controls_only" (case insensitive).  If it is "vertical", then the
096 controls are placed above the visual elements of the Placeable actors.
097 This is the default.  If it is "horizontal", then the controls
098 are placed to the left of the visual elements.  If it is "controls_only"
099 then no visual elements are placed.
100
101 <li>
102 <i>autoRun</i>: This can have value "true", or "false".  If it is
103 "true", then the model will be run when the applet's start() method is
104 called.  If false, then the model will not be run automatically.  The
105 default is "true".
106 </ul>
107
108 <p>
109 To create a model in a different way, say without a <i>modelClass</i>
110 applet parameter, you may extend this class and override the
111 protected method _createModel().  If you wish to alter the way
112 that the model is represented on the screen, you can extend this
113 class an override the _createView() method.  The rendition in this class
114 is an instance of ModelPane.
115 <p>
116 This class provides a number of methods that might be useful even
117 if its init() or _createModel() methods are not appropriate for a
118 given applet.  Specifically, it provides a mechanism for reporting
119 errors and exceptions; and it provide an applet parameter for
120 controlling the background color.
121
122 @see ModelPane
123 @see Placeable
124 @author Edward A. Lee
125 @version $Id$
126 @since Ptolemy II 0.3
127 @Pt.ProposedRating Green (eal)
128 @Pt.AcceptedRating Yellow (johnr)
129 */
130@SuppressWarnings("serial")
131public class PtolemyApplet extends BasicJApplet implements ExecutionListener {
132    ///////////////////////////////////////////////////////////////////
133    ////                         public methods                    ////
134
135    /** Cleanup after execution of the model.  This method is called
136     *  by the browser or appletviewer to inform this applet that
137     *  it should clean up.
138     */
139    @Override
140    public void destroy() {
141        // Note: we used to call manager.terminate() here to get rid
142        // of a lingering browser problem.
143        stop();
144        // Coverity suggests calling super.destroy() here.
145        // In the super.super class, java.applet.Applet.destroy() does nothing.
146        super.destroy();
147    }
148
149    /** Report that an execute error occurred.  This is
150     *  called by the manager.
151     *  @param manager The manager in charge of the execution.
152     *  @param throwable The throwable that triggered the error.
153     */
154    @Override
155    public void executionError(Manager manager, Throwable throwable) {
156        report(throwable);
157    }
158
159    /** Report that execution of the model has finished.  This is
160     *  called by the manager.
161     *  @param manager The manager in charge of the execution.
162     */
163    @Override
164    public void executionFinished(Manager manager) {
165        report("execution finished.");
166    }
167
168    /** Return a string describing this applet.
169     *  @return A string describing the applet.
170     */
171    @Override
172    public String getAppletInfo() {
173        return "Ptolemy applet for Ptolemy II "
174                + VersionAttribute.CURRENT_VERSION
175                + "\nPtolemy II comes from UC Berkeley, Department of EECS.\n"
176                + "See http://ptolemy.eecs.berkeley.edu/ptolemyII"
177                + "\n(Build: $Id$)";
178    }
179
180    /** Describe the applet parameters.
181     *  @return An array describing the applet parameters.
182     */
183    @Override
184    public String[][] getParameterInfo() {
185        String[][] newInfo = {
186                { "modelClass", "", "Class name for an instance of NamedObj" },
187                { "orientation", "",
188                        "Orientation: vertical, horizontal, or controls_only" },
189                { "controls", "", "List of on-screen controls" },
190                { "autoRun", "boolean",
191                        "Determines if the model is run automatically" } };
192        return _concatStringArrays(super.getParameterInfo(), newInfo);
193    }
194
195    /** Initialize the applet. This method is called by the browser
196     *  or applet viewer to inform this applet that it has been
197     *  loaded into the system. It is always called before
198     *  the first time that the start() method is called.
199     *  In this base class, this method creates a new workspace,
200     *  and instantiates in it the model whose class name is given
201     *  by the <i>modelClass</i> applet parameter.  If that model
202     *  does not contain a manager, then this method creates one for it.
203     */
204    @Override
205    public void init() {
206        super.init();
207        _setupOK = true;
208        _workspace = new Workspace(getClass().getName());
209
210        try {
211            _toplevel = _createModel(_workspace);
212
213            _toplevel.setModelErrorHandler(new BasicModelErrorHandler());
214
215            // This might not actually be a top level, because we might
216            // be looking inside.  So we check before creating a manager.
217            if (_toplevel.getContainer() == null
218                    && _toplevel instanceof CompositeActor) {
219                if (((CompositeActor) _toplevel).getManager() == null) {
220                    _manager = new Manager(_workspace, "manager");
221                    _manager.addExecutionListener(this);
222                    ((CompositeActor) _toplevel).setManager(_manager);
223                } else {
224                    _manager = ((CompositeActor) _toplevel).getManager();
225                }
226            }
227        } catch (Exception ex) {
228            _setupOK = false;
229            report("Creation of model failed:\n", ex);
230        }
231
232        _createView();
233    }
234
235    /** Report that the manager state has changed.  This is
236     *  called by the manager.
237     */
238    @Override
239    public void managerStateChanged(Manager manager) {
240        Manager.State newState = manager.getState();
241
242        if (newState != _previousState) {
243            report(manager.getState().getDescription());
244            _previousState = newState;
245        }
246    }
247
248    /** Start execution of the model. This method is called by the
249     *  browser or applet viewer to inform this applet that it should
250     *  start its execution. It is called after the init method and
251     *  each time the applet is revisited in a Web page.  In this base
252     *  class, this method calls the protected method _go(), which
253     *  executes the model, unless the noAutoRun parameter has been
254     *  set.  If a derived class does not wish to execute the model
255     *  each time start() is called, it should override this method
256     *  with a blank method.
257     */
258    @Override
259    public void start() {
260        // If an exception occurred during init, do not execute.
261        if (!_setupOK) {
262            return;
263        }
264
265        String autoRunSpec = getParameter("autoRun");
266
267        // Default is to run automatically.
268        boolean autoRun = true;
269
270        if (autoRunSpec != null) {
271            autoRun = Boolean.valueOf(autoRunSpec).booleanValue();
272        }
273
274        if (autoRun) {
275            try {
276                _go();
277            } catch (Exception ex) {
278                report(ex);
279            }
280        }
281    }
282
283    /** Stop execution of the model. This method is called by the
284     *  browser or applet viewer to inform this applet that it should
285     *  stop its execution. It is called when the Web page
286     *  that contains this applet has been replaced by another page,
287     *  and also just before the applet is to be destroyed.
288     *  In this base class, this method calls the stop() method
289     *  of the manager. If there is no manager, do nothing.
290     */
291    @Override
292    public void stop() {
293        if (_manager != null && _setupOK) {
294            _manager.stop();
295        }
296    }
297
298    ///////////////////////////////////////////////////////////////////
299    ////                         protected methods                 ////
300
301    /** Create a model.  In this base class, we check to see whether
302     *  the applet has a parameter <i>modelClass</i>, and if so, then we
303     *  instantiate the class specified in that parameter.  If not,
304     *  then we create an empty instance of NamedObj.
305     *  It is required that the class specified in the modelClass
306     *  parameter have a constructor that takes one argument, an instance
307     *  of Workspace.
308     *  In either case, if the resulting model does not have a manager,
309     *  then we give it a manager.
310     *  @param workspace The workspace in which to create the model.
311     *  @return A model.
312     *  @exception Exception If something goes wrong.  This is a broad
313     *   exception to allow derived classes wide latitude as to which
314     *   exception to throw.
315     */
316    protected NamedObj _createModel(Workspace workspace) throws Exception {
317        NamedObj result = null;
318
319        // Look for modelClass applet parameter.
320        String modelSpecification = getParameter("modelClass");
321
322        if (modelSpecification != null) {
323            Object[] arguments = new Object[1];
324            arguments[0] = workspace;
325
326            Class modelClass = Class.forName(modelSpecification);
327            Constructor[] constructors = modelClass.getConstructors();
328            boolean foundConstructor = false;
329
330            for (Constructor constructor : constructors) {
331                Class[] parameterTypes = constructor.getParameterTypes();
332
333                if (parameterTypes.length != arguments.length) {
334                    continue;
335                }
336
337                boolean match = true;
338
339                for (int j = 0; j < parameterTypes.length; j++) {
340                    if (!parameterTypes[j].isInstance(arguments[j])) {
341                        match = false;
342                        break;
343                    }
344                }
345
346                if (match) {
347                    result = (NamedObj) constructor.newInstance(arguments);
348                    foundConstructor = true;
349                }
350            }
351
352            if (!foundConstructor) {
353                throw new IllegalActionException(
354                        "Cannot find a suitable constructor for "
355                                + modelSpecification);
356            }
357        }
358
359        // If result is still null, then there was no modelClass given.
360        if (result == null) {
361            // Historical applets might directly define a model.
362            if (_toplevel == null) {
363                throw new Exception("Applet does not specify a model.");
364            } else {
365                return _toplevel;
366            }
367        }
368
369        return result;
370    }
371
372    /** Create run controls in a panel and return that panel.
373     *  The argument controls how many buttons are
374     *  created.  If its value is greater than zero, then a "Go" button
375     *  created.  If its value is greater than one, then a "Stop" button
376     *  is also created.  Derived classes may override this method to add
377     *  additional controls, or to create a panel with a different layout.
378     *  @param numberOfButtons How many buttons to create.
379     *  @return The run control panel.
380     *  @deprecated Use the <i>control</i> applet parameter.
381     */
382    @Deprecated
383    protected JPanel _createRunControls(int numberOfButtons) {
384        JPanel panel = new JPanel();
385
386        if (numberOfButtons > 0) {
387            _goButton = new JButton("Go");
388            panel.add(_goButton);
389            _goButton.addActionListener(new GoButtonListener());
390        }
391
392        if (numberOfButtons > 1) {
393            _stopButton = new JButton("Stop");
394            panel.add(_stopButton);
395            _stopButton.addActionListener(new StopButtonListener());
396        }
397
398        return panel;
399    }
400
401    /** Create a ModelPane to control execution of the model and display
402     *  its results.  Derived classes may override this to do something
403     *  different.
404     */
405    protected void _createView() {
406        // Parse applet parameters that determine visual appearance.
407        // Here, these are only relevant if the model is an instance
408        // of CompositeActor, since we create run panel controls.
409        if (!(_toplevel instanceof CompositeActor)) {
410            return;
411        }
412
413        // Start with orientation.
414        String orientationSpec = getParameter("orientation");
415
416        // Default is vertical
417        int orientation = ModelPane.VERTICAL;
418
419        if (orientationSpec != null) {
420            String lowerCaseOrientationSpecTrim = orientationSpec.trim()
421                    .toLowerCase(Locale.getDefault());
422            if (lowerCaseOrientationSpecTrim.equals("horizontal")) {
423                orientation = ModelPane.HORIZONTAL;
424            } else if (lowerCaseOrientationSpecTrim.equals("controls_only")) {
425                orientation = ModelPane.CONTROLS_ONLY;
426            }
427        }
428
429        // Next do controls.
430        String controlsSpec = getParameter("controls");
431
432        // Default has only the buttons.
433        int controls = ModelPane.BUTTONS;
434
435        if (controlsSpec != null) {
436            // If controls are given, then buttons need to be explicit.
437            controls = 0;
438
439            StringTokenizer tokenizer = new StringTokenizer(controlsSpec, ",");
440
441            while (tokenizer.hasMoreTokens()) {
442                String controlSpec = tokenizer.nextToken().trim()
443                        .toLowerCase(Locale.getDefault());
444
445                if (controlSpec.equals("buttons")) {
446                    controls = controls | ModelPane.BUTTONS;
447                } else if (controlSpec.equals("topparameters")) {
448                    controls = controls | ModelPane.TOP_PARAMETERS;
449                } else if (controlSpec.equals("directorparameters")) {
450                    controls = controls | ModelPane.DIRECTOR_PARAMETERS;
451                } else if (controlSpec.equals("none")) {
452                    controls = 0;
453                } else {
454                    report("Warning: unrecognized controls: " + controlSpec);
455                }
456            }
457        }
458
459        ModelPane pane = new ModelPane((CompositeActor) _toplevel, orientation,
460                controls);
461        pane.setBackground(null);
462        getContentPane().add(pane);
463    }
464
465    /** Execute the model, if the manager is not currently executing.
466     *  Note that this method is not called if there are button controls
467     *  on the screen and the user pushes the "Go" button.
468     *  @exception IllegalActionException Not thrown in this base class.
469     */
470    protected void _go() throws IllegalActionException {
471        // If an exception occurred during init, do not execute.
472        if (!_setupOK) {
473            return;
474        }
475
476        // Only try to start if there is no execution currently running.
477        if (_manager.getState() == Manager.IDLE) {
478            _manager.startRun();
479        }
480    }
481
482    /** Stop the execution.
483     */
484    protected void _stop() {
485        // If an exception occurred during init, do not finish.
486        if (!_setupOK) {
487            return;
488        }
489
490        _manager.stop();
491    }
492
493    ///////////////////////////////////////////////////////////////////
494    ////                         protected variables               ////
495
496    /** The manager, created in the init() method. */
497    protected Manager _manager;
498
499    /** Set this to false if the setup of the model during the init()
500     *  method fails.  This prevents the model from executing.
501     */
502    protected boolean _setupOK = true;
503
504    /** The top-level composite actor, created in the init() method. */
505    protected NamedObj _toplevel;
506
507    /** The workspace that the applet is built in. Each applet has
508     *  it own workspace.
509     */
510    protected Workspace _workspace;
511
512    ///////////////////////////////////////////////////////////////////
513    ////                         private variables                 ////
514    private JButton _goButton;
515
516    private JButton _stopButton;
517
518    private Manager.State _previousState;
519
520    ///////////////////////////////////////////////////////////////////
521    ////                         inner classes                     ////
522    private class GoButtonListener implements ActionListener {
523        @Override
524        public void actionPerformed(ActionEvent event) {
525            try {
526                _go();
527            } catch (Exception ex) {
528                report(ex);
529            }
530        }
531    }
532
533    private class StopButtonListener implements ActionListener {
534        @Override
535        public void actionPerformed(ActionEvent event) {
536            _stop();
537        }
538    }
539}