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}