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}