001/* An atomic actor that executes a model specified by a file or URL. 002 003 Copyright (c) 2003-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 028 */ 029package ptolemy.vergil.actor.lib; 030 031import javax.swing.JFrame; 032import javax.swing.SwingUtilities; 033 034import ptolemy.actor.CompositeActor; 035import ptolemy.actor.gui.Configuration; 036import ptolemy.actor.gui.Effigy; 037import ptolemy.actor.gui.PtolemyEffigy; 038import ptolemy.actor.gui.Tableau; 039import ptolemy.actor.gui.TableauFactory; 040import ptolemy.actor.gui.TableauFrame; 041import ptolemy.actor.lib.hoc.ModelReference; 042import ptolemy.data.expr.StringParameter; 043import ptolemy.gui.Top; 044import ptolemy.kernel.CompositeEntity; 045import ptolemy.kernel.util.Attribute; 046import ptolemy.kernel.util.IllegalActionException; 047import ptolemy.kernel.util.InternalErrorException; 048import ptolemy.kernel.util.KernelException; 049import ptolemy.kernel.util.NameDuplicationException; 050import ptolemy.kernel.util.NamedObj; 051import ptolemy.kernel.util.Workspace; 052import ptolemy.vergil.basic.ExtendedGraphFrame; 053 054/////////////////////////////////////////////////////////////////// 055//// VisualModelReference 056 057/** 058 This is an atomic actor that can execute and/or open a model specified by 059 a file or URL. This can be used to define an actor whose firing behavior 060 is given by a complete execution of another model. It extends the base 061 class with the following attributes and associated capabilities. 062 <ul> 063 <li> <i>openOnFiring</i>: 064 The value of this string attribute determines what open 065 happens when the fire() method is invoked. The recognized 066 values are: 067 <ul> 068 <li> "do not open" (the default) </li> 069 <li> "open in Vergil" </li> 070 <li> "open in Vergil (full screen)" </li> 071 </ul> 072 Note that it is dangerous to use the full-screen mode because it 073 becomes difficult to stop execution of the model that contains this 074 actor. In full-screen mode, the referenced model will consume 075 the entire screen. Stopping that execution will only serve to 076 stop the current iteration, and very likely, another iteration will 077 begin immediately and again occupy the entire screen. 078 Use this option with care. 079 </li> 080 <li> <i>closeOnPostfire</i>: 081 The value of this string attribute determines what happens 082 in the postfire() method. The recognized values are: 083 <ul> 084 <li> "do nothing" (the default) </li> 085 <li> "close Vergil graph" </li> 086 </ul> 087 </li> 088 </ul> 089 090 091 @author Edward A. Lee, Elaine Cheong 092 @version $Id$ 093 @since Ptolemy II 4.0 094 @Pt.ProposedRating Yellow (eal) 095 @Pt.AcceptedRating Red (eal) 096 @see ptolemy.data.expr.Variable 097 @see ptolemy.data.expr.Parameter 098 @see ptolemy.kernel.util.Settable 099 */ 100public class VisualModelReference extends ModelReference { 101 /** Construct a VisualModelReference with a name and a container. 102 * The container argument must not be null, or a 103 * NullPointerException will be thrown. This actor will use the 104 * workspace of the container for synchronization and version counts. 105 * If the name argument is null, then the name is set to the empty string. 106 * Increment the version of the workspace. This actor will have no 107 * local director initially, and its executive director will be simply 108 * the director of the container. 109 * 110 * @param container The container. 111 * @param name The name of this actor. 112 * @exception IllegalActionException If the container is incompatible 113 * with this actor. 114 * @exception NameDuplicationException If the name coincides with 115 * an actor already in the container. 116 */ 117 public VisualModelReference(CompositeEntity container, String name) 118 throws IllegalActionException, NameDuplicationException { 119 super(container, name); 120 121 // Create the openOnFiring parameter. 122 openOnFiring = new StringParameter(this, "openOnFiring"); 123 124 // Set the options for the parameters. 125 openOnFiring.setExpression("do not open"); 126 openOnFiring.addChoice("do not open"); 127 openOnFiring.addChoice("open in Vergil"); 128 openOnFiring.addChoice("open in Vergil (full screen)"); 129 130 // Create the closeOnPostfire parameter. 131 closeOnPostfire = new StringParameter(this, "closeOnPostfire"); 132 closeOnPostfire.setExpression("do nothing"); 133 closeOnPostfire.addChoice("do nothing"); 134 closeOnPostfire.addChoice("close Vergil graph"); 135 136 // Create a tableau factory to override look inside behavior. 137 new LookInside(this, "_lookInsideOverride"); 138 } 139 140 /////////////////////////////////////////////////////////////////// 141 //// parameters //// 142 143 /** The value of this string parameter determines what open 144 * happens when the fire() method is invoked. The recognized 145 * values are: 146 * <ul> 147 * <li> "do not open" (the default) </li> 148 * <li> "open in Vergil" </li> 149 * <li> "open in Vergil (full screen)" </li> 150 * </ul> 151 */ 152 public StringParameter openOnFiring; 153 154 /** The value of this string parameter determines what close action 155 * happens in the postfire() method. The recognized values are: 156 * <ul> 157 * <li> "do nothing" (the default) </li> 158 * <li> "close Vergil graph" </li> 159 * </ul> 160 */ 161 public StringParameter closeOnPostfire; 162 163 /////////////////////////////////////////////////////////////////// 164 //// public methods //// 165 166 /** Override the base class to open the model specified if the 167 * attribute is modelFileOrURL, or for other parameters, to cache 168 * their values. 169 * @param attribute The attribute that changed. 170 * @exception IllegalActionException If the change is not acceptable 171 * to this container (not thrown in this base class). 172 */ 173 @Override 174 public void attributeChanged(Attribute attribute) 175 throws IllegalActionException { 176 if (attribute == openOnFiring) { 177 String openOnFiringValue = openOnFiring.stringValue(); 178 179 if (openOnFiringValue.equals("do not open")) { 180 _openOnFiringValue = _DO_NOT_OPEN; 181 } else if (openOnFiringValue.equals("open in Vergil")) { 182 _openOnFiringValue = _OPEN_IN_VERGIL; 183 } else if (openOnFiringValue 184 .equals("open in Vergil (full screen)")) { 185 _openOnFiringValue = _OPEN_IN_VERGIL_FULL_SCREEN; 186 } else { 187 throw new IllegalActionException(this, 188 "Unrecognized option for openOnFiring: " 189 + openOnFiringValue); 190 } 191 } else if (attribute == closeOnPostfire) { 192 String closeOnPostfireValue = closeOnPostfire.stringValue(); 193 194 if (closeOnPostfireValue.equals("do nothing")) { 195 _closeOnPostfireValue = _DO_NOTHING; 196 } else if (closeOnPostfireValue.equals("close Vergil graph")) { 197 _closeOnPostfireValue = _CLOSE_VERGIL_GRAPH; 198 } else { 199 throw new IllegalActionException(this, 200 "Unrecognized option for closeOnPostfire: " 201 + closeOnPostfireValue); 202 } 203 } 204 if (attribute == modelFileOrURL) { 205 super.attributeChanged(attribute); 206 // If there was previously an effigy or tableau 207 // associated with this model, then delete them. 208 // In order to avoid deleting a newly created one, 209 // we use the very dangerous invokeAndWait(). 210 if (_effigy != null || _tableau != null) { 211 // NOTE: The closing must occur in the event thread. 212 Runnable doClose = new Runnable() { 213 @Override 214 public void run() { 215 // Have to repeat the test to be safe. 216 if (_effigy != null) { 217 try { 218 _effigy.setContainer(null); 219 } catch (NameDuplicationException | IllegalActionException e) { 220 throw new InternalErrorException(e); 221 } 222 _effigy = null; 223 } 224 if (_tableau != null) { 225 _tableau.close(); 226 try { 227 _tableau.setContainer(null); 228 } catch (NameDuplicationException | IllegalActionException e) { 229 throw new InternalErrorException(e); 230 } 231 _tableau = null; 232 } 233 } 234 }; 235 236 try { 237 if (!SwingUtilities.isEventDispatchThread()) { 238 SwingUtilities.invokeAndWait(doClose); 239 } else { 240 // Exporting HTML for ptolemy/actor/lib/hoc/demo/ModelReference/ModelReference.xml 241 // ends up running this in the Swing event dispatch thread. 242 doClose.run(); 243 } 244 245 } catch (Exception ex) { 246 throw new IllegalActionException(this, null, ex, 247 "Open failed."); 248 } 249 } 250 } else { 251 super.attributeChanged(attribute); 252 } 253 } 254 255 /** Clone this actor into the specified workspace. 256 * Override the base class to ensure that private variables are 257 * reset to null. 258 * @param workspace The workspace for the cloned object. 259 * @return A new instance of VisualModelReference. 260 * @exception CloneNotSupportedException If a derived class contains 261 * an attribute that cannot be cloned. 262 */ 263 @Override 264 public Object clone(Workspace workspace) throws CloneNotSupportedException { 265 VisualModelReference newActor = (VisualModelReference) super.clone( 266 workspace); 267 newActor._tableau = null; 268 newActor._effigy = null; 269 return newActor; 270 } 271 272 /** Run a complete execution of the referenced model. A complete 273 * execution consists of invocation of super.initialize(), repeated 274 * invocations of super.prefire(), super.fire(), and super.postfire(), 275 * followed by super.wrapup(). The invocations of prefire(), fire(), 276 * and postfire() are repeated until either the model indicates it 277 * is not ready to execute (prefire() returns false), or it requests 278 * a stop (postfire() returns false or stop() is called). 279 * Before running the complete execution, this method examines input 280 * ports, and if they are connected, have data, and if the referenced 281 * model has a top-level parameter with the same name, then one token 282 * is read from the input port and used to set the value of the 283 * parameter in the referenced model. 284 * After running the complete execution, if there are any output ports, 285 * then this method looks for top-level parameters in the referenced 286 * model with the same name as the output ports, and if there are any, 287 * reads their values and produces them on the output. 288 * If no model has been specified, then this method does nothing. 289 * @exception IllegalActionException If there is no director, or if 290 * the director's action methods throw it. 291 */ 292 @Override 293 public void fire() throws IllegalActionException { 294 // NOTE: Even though the superclass calls this, we have to 295 // call it here before the actions below. Regrettably, 296 // this requires disabling the call in the superclass 297 // because otherwise, if there are two pending input 298 // tokens, they will both be consumed in this firing. 299 _readInputsAndValidateSettables(); 300 _alreadyReadInputs = true; 301 302 if (_model instanceof CompositeActor) { 303 // Will need the effigy for the model this actor is in. 304 NamedObj toplevel = toplevel(); 305 final Effigy myEffigy = Configuration.findEffigy(toplevel); 306 307 // If there is no such effigy, then skip trying to open a tableau. 308 // The model may have no graphical elements. 309 if (myEffigy != null) { 310 try { 311 // Conditionally show the model in Vergil. The openModel() 312 // method also creates the right effigy. 313 if (_openOnFiringValue == _OPEN_IN_VERGIL 314 || _openOnFiringValue == _OPEN_IN_VERGIL_FULL_SCREEN) { 315 // NOTE: The opening must occur in the event thread. 316 // Regrettably, we cannot continue with the firing until 317 // the open is complete, so we use the very dangerous 318 // invokeAndWait() method. 319 Runnable doOpen = new Runnable() { 320 @Override 321 public void run() { 322 Configuration configuration = (Configuration) myEffigy 323 .toplevel(); 324 325 if (_debugging) { 326 _debug("** Using the configuration to open a tableau."); 327 } 328 329 try { 330 // NOTE: Executing this in the event thread averts 331 // a race condition... Previous close(), which was 332 // deferred to the UI thread, will have completed. 333 _exception = null; 334 _tableau = configuration.openModel(_model, 335 myEffigy); 336 337 // Set this tableau to be a master so that when it 338 // gets closed, all its subwindows get closed. 339 _tableau.setMaster(true); 340 } catch (KernelException e) { 341 // Record the exception for later reporting. 342 _exception = e; 343 } 344 345 if (_tableau != null) { 346 _tableau.show(); 347 348 JFrame frame = _tableau.getFrame(); 349 350 if (frame != null) { 351 if (_openOnFiringValue == _OPEN_IN_VERGIL_FULL_SCREEN) { 352 if (frame instanceof ExtendedGraphFrame) { 353 ((ExtendedGraphFrame) frame) 354 .fullScreen(); 355 } 356 } 357 358 frame.toFront(); 359 } 360 } 361 } 362 }; 363 364 try { 365 if (!SwingUtilities.isEventDispatchThread()) { 366 SwingUtilities.invokeAndWait(doOpen); 367 } else { 368 // Exporting HTML for ptolemy/actor/lib/hoc/demo/ModelReference/ModelReference.xml 369 // ends up running this in the Swing event dispatch thread. 370 doOpen.run(); 371 } 372 373 } catch (Exception ex) { 374 throw new IllegalActionException(this, null, ex, 375 "Open failed."); 376 } 377 378 if (_exception != null) { 379 // An exception occurred while trying to open. 380 throw new IllegalActionException(this, null, 381 _exception, "Failed to open."); 382 } 383 } else { 384 385 // Need an effigy for the model, or else 386 // graphical elements of the model will not 387 // work properly. That effigy needs to be 388 // contained by the effigy responsible for 389 // this actor. 390 391 if (_effigy == null) { 392 _effigy = new PtolemyEffigy(myEffigy, 393 myEffigy.uniqueName(_model.getName())); 394 _effigy.setModel(_model); 395 396 // Since there is no tableau, this is probably not 397 // necessary, but as a safety precaution, we prevent 398 // writing of the model. 399 _effigy.setModifiable(false); 400 401 if (_debugging) { 402 _debug("** Created new effigy for referenced model."); 403 } 404 } 405 } 406 } catch (NameDuplicationException ex) { 407 // This should not be thrown. 408 throw new InternalErrorException(ex); 409 } 410 } 411 } 412 413 // Call this last so that we open before executing. 414 super.fire(); 415 } 416 417 /** Override the base class to perform requested close on postfire actions. 418 * Note that if a close is requested, then this method waits until the 419 * AWT event thread completes the close. This creates the possibility 420 * of a deadlock. 421 * @return Whatever the superclass returns (probably true). 422 * @exception IllegalActionException Thrown if a parent class throws it. 423 */ 424 @Override 425 public boolean postfire() throws IllegalActionException { 426 // Call this first so execution stops before closing. 427 boolean result = super.postfire(); 428 429 if (_tableau != null) { 430 final JFrame frame = _tableau.getFrame(); 431 432 if (_closeOnPostfireValue == _CLOSE_VERGIL_GRAPH) { 433 if (_debugging) { 434 _debug("** Closing Vergil graph."); 435 } 436 437 if (frame instanceof TableauFrame) { 438 // NOTE: The closing will happen in the swing event 439 // thread. We can proceed on the assumption 440 // that the next firing, if it opens vergil, will 441 // do so in the event thread. 442 Runnable doClose = new Runnable() { 443 @Override 444 public void run() { 445 if (frame instanceof ExtendedGraphFrame) { 446 ((ExtendedGraphFrame) frame).cancelFullScreen(); 447 } 448 449 ((TableauFrame) frame).close(); 450 } 451 }; 452 453 Top.deferIfNecessary(doClose); 454 } else if (frame != null) { 455 // This should be done in the event thread. 456 Runnable doClose = new Runnable() { 457 @Override 458 public void run() { 459 if (frame instanceof ExtendedGraphFrame) { 460 ((ExtendedGraphFrame) frame).cancelFullScreen(); 461 } 462 463 frame.setVisible(true); 464 } 465 }; 466 467 Top.deferIfNecessary(doClose); 468 } 469 } 470 } 471 472 return result; 473 } 474 475 /////////////////////////////////////////////////////////////////// 476 //// protected variables //// 477 478 /** Tableau that has been created (if any). */ 479 protected Tableau _tableau; 480 481 /////////////////////////////////////////////////////////////////// 482 //// private variables //// 483 484 // Possible values for openOnFiring. 485 private static int _DO_NOT_OPEN = 0; 486 private static int _OPEN_IN_VERGIL = 1; 487 private static int _OPEN_IN_VERGIL_FULL_SCREEN = 2; 488 489 /** The value of the openOnFiring parameter. */ 490 private transient int _openOnFiringValue = _DO_NOT_OPEN; 491 492 // Possible values for closeOnPostfire. 493 private static int _DO_NOTHING = 0; 494 495 private static int _CLOSE_VERGIL_GRAPH = 1; 496 497 /** The value of the closeOnPostfire parameter. */ 498 private transient int _closeOnPostfireValue = _DO_NOTHING; 499 500 /** Store exception thrown in event thread. */ 501 private Exception _exception = null; 502 503 /** Effigy that has been created (if any). */ 504 private PtolemyEffigy _effigy; 505 506 /////////////////////////////////////////////////////////////////// 507 //// inner classes //// 508 509 /** A tableau factory to override the look inside behavior to open 510 * the referenced model, if there is one. 511 */ 512 public class LookInside extends TableauFactory { 513 /** 514 * Construct a VisualModelReference$LookInside object. 515 * 516 * @param container The container of the LookInside to be constructed. 517 * @param name The name of the LookInside to be constructed. 518 * @exception IllegalActionException If thrown by the superclass. 519 * @exception NameDuplicationException If thrown by the superclass. 520 */ 521 public LookInside(NamedObj container, String name) 522 throws IllegalActionException, NameDuplicationException { 523 super(container, name); 524 } 525 526 /** Open an instance of the model. 527 * @param effigy The effigy with which we open the model. 528 * @return The instance of the model. 529 * @exception Exception If there is a problem opening the 530 * model. 531 */ 532 @Override 533 public Tableau createTableau(Effigy effigy) throws Exception { 534 if (_model == null) { 535 throw new IllegalActionException(VisualModelReference.this, 536 "No model referenced."); 537 } 538 Configuration configuration = (Configuration) effigy.toplevel(); 539 return configuration.openInstance(_model, effigy); 540 } 541 } 542}