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}