001/* An actor that displays input data in a text area on the screen.
002
003 @Copyright (c) 1998-2014 The Regents of the University of California.
004 All rights reserved.
005
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the
009 above copyright notice and the following two paragraphs appear in all
010 copies of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION 2
026 COPYRIGHTENDKEY
027 */
028
029package ptolemy.actor.lib.gui;
030
031import java.util.HashSet;
032import java.util.Set;
033
034import ptolemy.actor.TypedAtomicActor;
035import ptolemy.actor.TypedIOPort;
036import ptolemy.actor.injection.ActorModuleInitializer;
037import ptolemy.actor.injection.PortableContainer;
038import ptolemy.actor.injection.PortablePlaceable;
039import ptolemy.actor.injection.PtolemyInjector;
040import ptolemy.data.BooleanToken;
041import ptolemy.data.IntToken;
042import ptolemy.data.StringToken;
043import ptolemy.data.Token;
044import ptolemy.data.expr.Parameter;
045import ptolemy.data.expr.StringParameter;
046import ptolemy.data.type.BaseType;
047import ptolemy.data.type.TypeConstant;
048import ptolemy.graph.Inequality;
049import ptolemy.kernel.CompositeEntity;
050import ptolemy.kernel.util.Attribute;
051import ptolemy.kernel.util.IllegalActionException;
052import ptolemy.kernel.util.InternalErrorException;
053import ptolemy.kernel.util.NameDuplicationException;
054import ptolemy.kernel.util.Nameable;
055import ptolemy.kernel.util.NamedObj;
056import ptolemy.kernel.util.Workspace;
057
058///////////////////////////////////////////////////////////////////
059//// Display
060
061/**
062 <p>
063 Display the values of the tokens arriving on the input channels in a
064 text area on the screen.  Each input token is written on a
065 separate line.  The input type can be of any type.
066 If the input happens to be a StringToken,
067 then the surrounding quotation marks are stripped before printing
068 the value of the token.  Thus, string-valued tokens can be used to
069 generate arbitrary textual output, at one token per line.
070 Tokens are read from the input only in
071 the postfire() method, to allow them to settle in domains where they
072 converge to a fixed point.
073 </p><p>
074  This actor accepts any type of data on its input port, therefore it
075 doesn't declare a type, but lets the type resolution algorithm find
076 the least fixed point. If backward type inference is enabled, and
077 no input type has been declared, the input is constrained to be
078 equal to <code>BaseType.GENERAL</code>. This will result in upstream
079 ports resolving to the most general type rather than the most specific.
080 </p><p>
081 This actor has a <i>suppressBlankLines</i> parameter, whose default value
082 is false. If this parameter is configured to be true, this actor does not
083 put a blank line in the display.
084 </p><p>
085 Note that because of complexities in Swing, if you resize the display
086 window, then, unlike the plotters, the new size will not be persistent.
087 That is, if you save the model and then re-open it, the new size is
088 forgotten.  To control the size, you should set the <i>rowsDisplayed</i>
089 and <i>columnsDisplayed</i> parameters.
090 </p><p>
091 Note that this actor internally uses JTextArea, a Java Swing object
092 that is known to consume large amounts of memory. It is not advisable
093 to use this actor to log large output streams.</p>
094
095 @author  Yuhong Xiong, Edward A. Lee Contributors: Ishwinder Singh
096 @version $Id$
097 @since Ptolemy II 1.0
098 @Pt.ProposedRating Yellow (yuhong)
099 @Pt.AcceptedRating Yellow (vogel)
100 */
101public class Display extends TypedAtomicActor implements PortablePlaceable {
102    /** Construct an actor with an input multiport of type GENERAL.
103     *  @param container The container.
104     *  @param name The name of this actor.
105     *  @exception IllegalActionException If the entity cannot be contained
106     *   by the proposed container.
107     *  @exception NameDuplicationException If the container already has an
108     *   actor with this name.
109     */
110
111    public Display(CompositeEntity container, String name)
112            throws IllegalActionException, NameDuplicationException {
113        super(container, name);
114
115        input = new TypedIOPort(this, "input", true, false);
116        input.setMultiport(true);
117        input.setAutomaticTypeConversion(false);
118
119        rowsDisplayed = new Parameter(this, "rowsDisplayed");
120        rowsDisplayed.setExpression("10");
121        columnsDisplayed = new Parameter(this, "columnsDisplayed");
122        columnsDisplayed.setExpression("40");
123
124        suppressBlankLines = new Parameter(this, "suppressBlankLines");
125        suppressBlankLines.setTypeEquals(BaseType.BOOLEAN);
126        suppressBlankLines.setToken(BooleanToken.FALSE);
127
128        title = new StringParameter(this, "title");
129        title.setExpression("");
130
131        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-20\" y=\"-15\" "
132                + "width=\"40\" height=\"30\" " + "style=\"fill:lightGrey\"/>\n"
133                + "<rect x=\"-15\" y=\"-10\" " + "width=\"30\" height=\"20\" "
134                + "style=\"fill:white\"/>\n"
135                + "<line x1=\"-13\" y1=\"-6\" x2=\"-4\" y2=\"-6\" "
136                + "style=\"stroke:grey\"/>\n"
137                + "<line x1=\"-13\" y1=\"-2\" x2=\"0\" y2=\"-2\" "
138                + "style=\"stroke:grey\"/>\n"
139                + "<line x1=\"-13\" y1=\"2\" x2=\"-8\" y2=\"2\" "
140                + "style=\"stroke:grey\"/>\n"
141                + "<line x1=\"-13\" y1=\"6\" x2=\"4\" y2=\"6\" "
142                + "style=\"stroke:grey\"/>\n" + "</svg>\n");
143    }
144
145    ///////////////////////////////////////////////////////////////////
146    ////        public variables and parameters                    ////
147
148    /** The horizontal size of the display, in columns. This contains
149     *  an integer, and defaults to 40.
150     */
151    public Parameter columnsDisplayed;
152
153    /** The input port, which is a multiport.
154     */
155    public TypedIOPort input;
156
157    /** The vertical size of the display, in rows. This contains an
158     *  integer, and defaults to 10.
159     */
160    public Parameter rowsDisplayed;
161
162    /** The flag indicating whether this display actor suppress
163     *  blank lines. The default value is false.
164     */
165    public Parameter suppressBlankLines;
166
167    /** The title to put on top. Note that the value of the title
168     *  overrides the value of the name of the actor or the display
169     *  name of the actor.
170     */
171    public StringParameter title;
172
173    ///////////////////////////////////////////////////////////////////
174    ////                         public methods                    ////
175
176    /** If the specified attribute is <i>rowsDisplayed</i>, then set
177     *  the desired number of rows of the textArea, if there is one.
178     *  @param attribute The attribute that has changed.
179     *  @exception IllegalActionException If the specified attribute
180     *   is <i>rowsDisplayed</i> and its value is not positive.
181     */
182    @Override
183    public void attributeChanged(Attribute attribute)
184            throws IllegalActionException {
185        if (attribute == rowsDisplayed) {
186            int numRows = ((IntToken) rowsDisplayed.getToken()).intValue();
187
188            if (numRows <= 0) {
189                throw new IllegalActionException(this,
190                        "rowsDisplayed: requires a positive value.");
191            }
192
193            if (numRows != _previousNumRows) {
194                _previousNumRows = numRows;
195
196                _getImplementation().setRows(numRows);
197
198            }
199        } else if (attribute == columnsDisplayed) {
200            int numColumns = ((IntToken) columnsDisplayed.getToken())
201                    .intValue();
202
203            if (numColumns <= 0) {
204                throw new IllegalActionException(this,
205                        "columnsDisplayed: requires a positive value.");
206            }
207
208            if (numColumns != _previousNumColumns) {
209                _previousNumColumns = numColumns;
210
211                _getImplementation().setColumns(numColumns);
212
213            }
214        } else if (attribute == suppressBlankLines) {
215            _isSuppressBlankLines = ((BooleanToken) suppressBlankLines
216                    .getToken()).booleanValue();
217        } else if (attribute == title) {
218            _getImplementation().setTitle(title.stringValue());
219        }
220
221    }
222
223    /** Free up memory when closing. */
224    public void cleanUp() {
225        _implementation.cleanUp();
226    }
227
228    /** Clone the actor into the specified workspace. This calls the
229     *  base class and then sets the textArea public variable to null.
230     *  @param workspace The workspace for the new object.
231     *  @return A new actor.
232     *  @exception CloneNotSupportedException If a derived class contains
233     *   an attribute that cannot be cloned.
234     */
235    @Override
236    public Object clone(Workspace workspace) throws CloneNotSupportedException {
237        Display newObject = (Display) super.clone(workspace);
238        newObject._implementation = null;
239        return newObject;
240    }
241
242    /** Initialize this display.  If place() has not been called
243     *  with a container into which to place the display, then create a
244     *  new frame into which to put it.
245     *  @exception IllegalActionException If the parent class throws it,
246     *   or if the numRows or numColumns parameters are incorrect, or
247     *   if there is no effigy for the top level container, or if a problem
248     *   occurs creating the effigy and tableau.
249     */
250    @Override
251    public void initialize() throws IllegalActionException {
252        super.initialize();
253        _initialized = false;
254    }
255
256    /** Specify the container into which this object should be placed.
257     *  Obviously, this method needs to be called before the object
258     *  is actually placed in a container.  Otherwise, the object will be
259     *  expected to create its own frame into which to place itself.
260     *  For actors, this method should be called before initialize().
261     *  @param container The container in which to place the object, or
262     *   null to specify that there is no current container.
263     */
264    @Override
265    public void place(PortableContainer container) {
266        _getImplementation().place(container);
267    }
268
269    /** Read at most one token from each input channel and display its
270     *  string value on the screen.  Each value is terminated
271     *  with a newline character.
272     *  @exception IllegalActionException If there is no director.
273     */
274    @Override
275    public boolean postfire() throws IllegalActionException {
276
277        int width = input.getWidth();
278
279        for (int i = 0; i < width; i++) {
280            String value = _getInputString(i);
281            if (value != null) {
282                // Do not open the display until there is a token.
283                if (!_initialized) {
284                    _initialized = true;
285                    _openWindow();
286                }
287                _implementation.display(value);
288            }
289
290            else if (!_isSuppressBlankLines) {
291                // There is no input token on this channel, so we
292                // output a blank line.
293                _implementation.display("");
294            }
295
296        }
297        // If we have a Const -> Display SDF model with iterations set
298        // to 0, then stopping the model by hitting the stop button
299        // was taking between 2 and 17 seconds (average over 11 runs, 7.2 seconds)
300        // If we have a Thread.yield() here, then the time is between
301        // 1.3 and 3.5 seconds ( average over 10 runs, 2.5 seconds).
302        // Unfortunately, this doesn't actually work... The textArea object
303        // may clutter the event thread with unprocessed events that delay
304        // response to the stop button.
305        Thread.yield();
306
307        return super.postfire();
308    }
309
310    /** Override the base class to remove the display from its graphical
311     *  container if the argument is null.
312     *  @param container The proposed container.
313     *  @exception IllegalActionException If the base class throws it.
314     *  @exception NameDuplicationException If the base class throws it.
315     */
316    @Override
317    public void setContainer(CompositeEntity container)
318            throws IllegalActionException, NameDuplicationException {
319        Nameable previousContainer = getContainer();
320        super.setContainer(container);
321
322        if (container != previousContainer && previousContainer != null) {
323            _remove();
324        }
325    }
326
327    /** Set a name to present to the user.
328     *  <p>If the <i>title</i> parameter is set to the empty string,
329     *  and the Display window has been rendered, then the title of the
330     *  Display window will be updated to the value of the name parameter.</p>
331     *  @param name A name to present to the user.
332     *  @see #getDisplayName()
333     */
334    @Override
335    public void setDisplayName(String name) {
336        super.setDisplayName(name);
337        // See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4302
338        _setTitle(name);
339    }
340
341    /** Set or change the name.  If a null argument is given the
342     *  name is set to an empty string.
343     *  Increment the version of the workspace.
344     *  This method is write-synchronized on the workspace.
345     *  <p>If the <i>title</i> parameter is set to the empty string,
346     *  and the Display window has been rendered, then the title of the
347     *  Display window will be updated to the value of the name parameter.</p>
348     *  @param name The new name.
349     *  @exception IllegalActionException If the name contains a period
350     *   or if the object is a derived object and the name argument does
351     *   not match the current name.
352     *  @exception NameDuplicationException Not thrown in this base class.
353     *   May be thrown by derived classes if the container already contains
354     *   an object with this name.
355     *  @see #getName()
356     *  @see #getName(NamedObj)
357     *  @see #title
358     */
359    @Override
360    public void setName(String name)
361            throws IllegalActionException, NameDuplicationException {
362        super.setName(name);
363        // See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4302
364        _setTitle(name);
365    }
366
367    ///////////////////////////////////////////////////////////////////
368    ////                         protected methods                 ////
369
370    /** Set the input port greater than or equal to
371     *  <code>BaseType.GENERAL</code> in case backward type inference is
372     *  enabled and the input port has no type declared.
373     *
374     *  @return A set of inequalities.
375     */
376    @Override
377    protected Set<Inequality> _customTypeConstraints() {
378        HashSet<Inequality> result = new HashSet<Inequality>();
379        if (isBackwardTypeInferenceEnabled()
380                && input.getTypeTerm().isSettable()) {
381            result.add(new Inequality(new TypeConstant(BaseType.GENERAL),
382                    input.getTypeTerm()));
383        }
384        return result;
385    }
386
387    /** Get the right instance of the implementation depending upon the
388     *  of the dependency specified through dependency injection.
389     *  If the instance has not been created, then it is created.
390     *  If the instance already exists then return the same.
391     *
392     *        <p>This code is used as part of the dependency injection needed for the
393     *  HandSimDroid project, see $PTII/ptserver.  This code uses dependency
394     *  inject to determine what implementation to use at runtime.
395     *  This method eventually reads ptolemy/actor/ActorModule.properties.
396     *  {@link ptolemy.actor.injection.ActorModuleInitializer#initializeInjector()}
397     *  should be called before this method is called.  If it is not
398     *  called, then a message is printed and initializeInjector() is called.</p>
399     *
400     *  @return the instance of the implementation.
401     */
402    protected DisplayInterface _getImplementation() {
403        if (_implementation == null) {
404            if (PtolemyInjector.getInjector() == null) {
405                System.err.println("Warning: main() did not call "
406                        + "ActorModuleInitializer.initializeInjector(), "
407                        + "so Display is calling it for you.");
408                ActorModuleInitializer.initializeInjector();
409            }
410            _implementation = PtolemyInjector.getInjector()
411                    .getInstance(DisplayInterface.class);
412            try {
413                _implementation.init(this);
414            } catch (NameDuplicationException e) {
415                throw new InternalErrorException(this, e,
416                        "Failed to initialize implementation");
417            } catch (IllegalActionException e) {
418                throw new InternalErrorException(this, e,
419                        "Failed to initialize implementation");
420            }
421        }
422        return _implementation;
423    }
424
425    /** Return a string describing the input on channel i.
426     *  This is a protected method to allow subclasses to override
427     *  how inputs are observed.
428     *  @param i The channel
429     *  @return A string representation of the input, or null
430     *   if there is nothing to display.
431     *  @exception IllegalActionException If reading the input fails.
432     */
433    protected String _getInputString(int i) throws IllegalActionException {
434        if (input.hasToken(i)) {
435            Token token = input.get(i);
436            String value = token.toString();
437            if (token instanceof StringToken) {
438                value = ((StringToken) token).stringValue();
439            }
440            return value;
441        }
442        return null;
443    }
444
445    /** Open the display window if it has not been opened.
446     *  @exception IllegalActionException If there is a problem creating
447     *  the effigy.
448     */
449    protected void _openWindow() throws IllegalActionException {
450        _getImplementation().openWindow();
451    }
452
453    ///////////////////////////////////////////////////////////////////
454    ////                         protected members                 ////
455
456    /** Indicator that the display window has been opened. */
457    protected boolean _initialized = false;
458
459    /** The flag indicating whether the blank lines will be suppressed. */
460    protected boolean _isSuppressBlankLines = false;
461
462    ///////////////////////////////////////////////////////////////////
463    ////                         private methods                   ////
464
465    /** Remove the display from the current container, if there is one.
466     */
467    private void _remove() {
468        _implementation.remove();
469    }
470
471    /** Set the title of this window.
472     *  <p>If the <i>title</i> parameter is set to the empty string,
473     *  and the Display window has been rendered, then the title of the
474     *  Display window will be updated to the value of the name parameter.</p>
475     */
476    private void _setTitle(String name) {
477
478        try {
479            _getImplementation().setTitle(name);
480        } catch (IllegalActionException ex) {
481            throw new InternalErrorException(this, ex,
482                    "Failed to get the value of the title parameter.");
483        }
484
485    }
486
487    ///////////////////////////////////////////////////////////////////
488    ////                         private members                   ////
489
490    // Implementation of the DisplayInterface
491    private DisplayInterface _implementation;
492
493    // Record of previous columns.
494    private int _previousNumColumns = 0;
495
496    // Record of previous rows.
497    private int _previousNumRows = 0;
498
499}