001/* A plotter that is also a source of sketched signals. 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 */ 028package ptolemy.actor.lib.gui; 029 030import ptolemy.actor.Manager; 031import ptolemy.actor.TypedIOPort; 032import ptolemy.actor.injection.PortableContainer; 033import ptolemy.data.ArrayToken; 034import ptolemy.data.BooleanToken; 035import ptolemy.data.DoubleToken; 036import ptolemy.data.IntToken; 037import ptolemy.data.Token; 038import ptolemy.data.expr.Parameter; 039import ptolemy.data.type.ArrayType; 040import ptolemy.data.type.BaseType; 041import ptolemy.kernel.CompositeEntity; 042import ptolemy.kernel.util.Attribute; 043import ptolemy.kernel.util.IllegalActionException; 044import ptolemy.kernel.util.InternalErrorException; 045import ptolemy.kernel.util.NameDuplicationException; 046import ptolemy.kernel.util.Settable; 047import ptolemy.kernel.util.Workspace; 048import ptolemy.plot.EditListener; 049import ptolemy.plot.EditablePlot; 050import ptolemy.plot.Plot; 051import ptolemy.plot.PlotBox; 052 053/////////////////////////////////////////////////////////////////// 054//// SketchedSource 055 056/** 057 This actor is a plotter that also produces as its output a 058 signal that has been sketched by the user on the screen. 059 The <i>length</i> parameter specifies the 060 number of samples in the sketched signal. The <i>periodic</i> 061 parameter, if true, specifies that the signal should be repeated. 062 If this parameter is false, then the sketched signal is produced 063 exactly once, at the beginning of the execution of the model. If 064 <i>periodic</i> is true and the sketch is modified during 065 execution of the model, then the modification appears in the next 066 cycle after the modification has been completed. In 067 other words, the change does not appear mid-cycle. 068 <p> 069 This actor is also a plotter, and will plot the input signals 070 on the same plot as the sketched signal. It can be used in a 071 feedback loop where the output affects the input. The first batch 072 of outputs is produced in the initialize() method, so it can 073 be put in a feedback loop in a dataflow model. 074 075 @author Edward A. Lee 076 @version $Id$ 077 @since Ptolemy II 1.0 078 @Pt.ProposedRating Yellow (eal) 079 @Pt.AcceptedRating Red (vogel) 080 */ 081public class SketchedSource extends SequencePlotter implements EditListener { 082 /** Construct an actor with the given container and name. 083 * @param container The container. 084 * @param name The name of this actor. 085 * @exception IllegalActionException If the actor cannot be contained 086 * by the proposed container. 087 * @exception NameDuplicationException If the container already has an 088 * actor with this name. 089 */ 090 public SketchedSource(CompositeEntity container, String name) 091 throws IllegalActionException, NameDuplicationException { 092 super(container, name); 093 094 output = new TypedIOPort(this, "output", false, true); 095 output.setTypeEquals(BaseType.DOUBLE); 096 097 // Create the parameters. 098 length = new Parameter(this, "length", new IntToken(100)); 099 length.setTypeEquals(BaseType.INT); 100 101 // The initial trace is used to make the sketched value 102 // persistent, and also to provide an initial trace when 103 // an instance of the actor is first dragged onto a model. 104 initialTrace = new Parameter(this, "initialTrace"); 105 initialTrace.setExpression("repeat(length, 0.0)"); 106 initialTrace.setTypeEquals(new ArrayType(BaseType.DOUBLE)); 107 initialTrace.setVisibility(Settable.EXPERT); 108 109 periodic = new Parameter(this, "periodic", BooleanToken.TRUE); 110 periodic.setTypeEquals(BaseType.BOOLEAN); 111 yBottom = new Parameter(this, "yBottom", new DoubleToken(-1.0)); 112 yBottom.setTypeEquals(BaseType.DOUBLE); 113 yTop = new Parameter(this, "yTop", new DoubleToken(1.0)); 114 yTop.setTypeEquals(BaseType.DOUBLE); 115 116 runOnModification = new Parameter(this, "runOnModification", 117 BooleanToken.FALSE); 118 runOnModification.setTypeEquals(BaseType.BOOLEAN); 119 120 // Fill on wrapup no longer makes sense. 121 // NOTE: This gets overridden with zero if the MoML file 122 // gives the value of this variable. Hence, we need to 123 // reset later as well. 124 fillOnWrapup.setToken(BooleanToken.FALSE); 125 fillOnWrapup.setVisibility(Settable.NONE); 126 127 // Starting data set for producing plots is now always 1. 128 // NOTE: This gets overridden with zero if the MoML file 129 // gives the value of this variable. Hence, we need to 130 // reset later as well. 131 startingDataset.setToken(_one); 132 startingDataset.setVisibility(Settable.NONE); 133 134 // Set the initial token production parameter of the 135 // output port so that this can be used in SDF in feedback 136 // loops. 137 Parameter tokenInitProduction = new Parameter(output, 138 "tokenInitProduction"); 139 140 // Use an expression here so change propagate. 141 tokenInitProduction.setExpression("length"); 142 } 143 144 /////////////////////////////////////////////////////////////////// 145 //// ports and parameters //// 146 147 /** The default signal to generate, prior to any user sketch. 148 * By default, this contains an array of zeros with the length 149 * given by the <i>length</i> parameter. 150 */ 151 public Parameter initialTrace; 152 153 /** The length of the output signal that will be generated. 154 * This parameter must contain an IntToken. By default, it has 155 * value 100. 156 */ 157 public Parameter length; 158 159 /** The output port. The type of this port is double. 160 */ 161 public TypedIOPort output = null; 162 163 /** An indicator of whether the signal should be periodically 164 * repeated. This parameter must contain a boolean token. 165 * By default, it has value true. 166 */ 167 public Parameter periodic; 168 169 /** If <i>true</i>, then when the user edits the plot, if the 170 * manager is currently idle, then run the model. 171 * This is a boolean that defaults to <i>false</i>. 172 */ 173 public Parameter runOnModification; 174 175 /** The bottom of the Y range. This is a double, with default value -1.0. 176 */ 177 public Parameter yBottom; 178 179 /** The top of the Y range. This is a double, with default value 1.0. 180 */ 181 public Parameter yTop; 182 183 /////////////////////////////////////////////////////////////////// 184 //// public methods //// 185 186 /** If the specified attribute is <i>length</i>, 187 * then set the trace to its initial value. 188 * @param attribute The attribute that changed. 189 * @exception IllegalActionException If the specified attribute 190 * is <i>length</i> and its value is not positive. 191 */ 192 @Override 193 public void attributeChanged(Attribute attribute) 194 throws IllegalActionException { 195 if (attribute == length) { 196 int lengthValue = ((IntToken) length.getToken()).intValue(); 197 198 if (lengthValue < 0) { 199 throw new IllegalActionException(this, 200 "length: value is required to be positive."); 201 } 202 203 if (lengthValue != _previousLengthValue) { 204 _previousLengthValue = lengthValue; 205 _initialTraceIsSet = false; 206 _showInitialTrace(); 207 } 208 } else if (attribute == yBottom || attribute == yTop) { 209 _setRanges(); 210 } else { 211 super.attributeChanged(attribute); 212 } 213 } 214 215 /** Clone the actor into the specified workspace. 216 * @param workspace The workspace for the new object. 217 * @return A new actor. 218 * @exception CloneNotSupportedException If a derived class has an 219 * attribute that cannot be cloned. 220 */ 221 @Override 222 public Object clone(Workspace workspace) throws CloneNotSupportedException { 223 SketchedSource newObject = (SketchedSource) super.clone(workspace); 224 _data = null; 225 _dataModified = false; 226 _count = 0; 227 _initialTraceIsSet = false; 228 _previousLengthValue = -1; 229 _settingInitialTrace = false; 230 return newObject; 231 } 232 233 /** React to the fact that data in the specified plot has been modified 234 * by a user edit action by recording the data. Note that this is 235 * typically called in the UI thread, and it is synchronized. 236 * @param source The plot containing the modified data. 237 * @param dataset The data set that has been modified. 238 */ 239 @Override 240 public synchronized void editDataModified(EditablePlot source, 241 int dataset) { 242 if (dataset == 0 && !_settingInitialTrace) { 243 _dataModified = true; 244 _data = ((EditablePlot) plot).getData(0); 245 246 // Optionally execute the model here if it is idle. 247 try { 248 boolean runValue = ((BooleanToken) runOnModification.getToken()) 249 .booleanValue(); 250 251 if (runValue) { 252 Manager manager = getManager(); 253 254 if (manager != null && manager.getState() == Manager.IDLE) { 255 // Instead of calling manager.startRun(), 256 // call manager.execute(). 257 // Otherwise applets have problems. 258 manager.execute(); 259 } 260 } 261 } catch (ptolemy.kernel.util.KernelException ex) { 262 // Should be thrown only if the manager is not idle, or 263 // if the parameter is not boolean valued. 264 throw new InternalErrorException(ex); 265 } 266 } 267 } 268 269 /** Produce one data sample from the sketched signal on the output 270 * port. 271 * @exception IllegalActionException If there is no director, or 272 * if the base class throws it. 273 */ 274 @Override 275 public void fire() throws IllegalActionException { 276 // Read the trigger input, if there is one. 277 super.fire(); 278 279 boolean periodicValue = ((BooleanToken) periodic.getToken()) 280 .booleanValue(); 281 282 // If this isn't periodic, then send zero only, since we already 283 // sent out the entire waveform in the initialize method. 284 if (!periodicValue) { 285 output.send(0, _zero); 286 return; 287 } 288 289 ArrayToken arrayToken = (ArrayToken) initialTrace.getToken(); 290 output.send(0, arrayToken.getElement(_count)); 291 _count++; 292 293 if (_count == arrayToken.length()) { 294 _count = 0; 295 _updateInitialTrace(); 296 } 297 } 298 299 /** Override the base class to read data from the plot and to 300 * produce all the data on the output. 301 * @exception IllegalActionException If the parent class throws it. 302 */ 303 @Override 304 public void initialize() throws IllegalActionException { 305 // NOTE: These gets overridden with zero after construction 306 // if the MoML file gives the value. 307 // Hence, we need to reset here as well. 308 startingDataset.setToken(_one); 309 fillOnWrapup.setToken(BooleanToken.FALSE); 310 311 super.initialize(); 312 313 if (!_initialTraceIsSet) { 314 _showInitialTrace(); 315 } 316 317 _updateInitialTrace(); 318 319 // Produce the data on the output so that this can be used in 320 // feedback look in dataflow models. 321 ArrayToken arrayToken = (ArrayToken) initialTrace.getToken(); 322 output.send(0, arrayToken.arrayValue(), arrayToken.length()); 323 324 _count = 0; 325 } 326 327 /** Override the base class to create an initial trace. 328 * @param container The container into which to place the plot. 329 */ 330 @Override 331 public void place(PortableContainer container) { 332 super.place(container); 333 334 if (container != null) { 335 // Set the default signal value in the plot. 336 try { 337 _showInitialTrace(); 338 } catch (IllegalActionException ex) { 339 throw new InternalErrorException(ex.getMessage()); 340 } 341 } 342 } 343 344 /** Override the base class to not clear the plot. The PlotterBase 345 * class clears the entire plot, which will erase sketched data. 346 * @exception IllegalActionException If triggered by creating receivers. 347 */ 348 @Override 349 public void preinitialize() throws IllegalActionException { 350 // This code is copied from AtomicActor, since we can't call super. 351 _stopRequested = false; 352 } 353 354 /////////////////////////////////////////////////////////////////// 355 //// protected methods //// 356 357 /** Create a new plot. In this class, it is an instance of EditablePlot. 358 * @return A new editable plot object. 359 */ 360 @Override 361 protected PlotBox _newPlot() { 362 EditablePlot result = new EditablePlot(); 363 result.addEditListener(this); 364 return result; 365 } 366 367 /////////////////////////////////////////////////////////////////// 368 //// private methods //// 369 // Set the X and Y ranges of the plot. 370 private void _setRanges() throws IllegalActionException { 371 if (plot == null) { 372 return; 373 } 374 375 double xInitValue = ((DoubleToken) xInit.getToken()).doubleValue(); 376 double xUnitValue = ((DoubleToken) xUnit.getToken()).doubleValue(); 377 int lengthValue = ((IntToken) length.getToken()).intValue(); 378 plot.setXRange(xInitValue, xUnitValue * lengthValue); 379 380 double yBottomValue = ((DoubleToken) yBottom.getToken()).doubleValue(); 381 double yTopValue = ((DoubleToken) yTop.getToken()).doubleValue(); 382 plot.setYRange(yBottomValue, yTopValue); 383 } 384 385 // Show the initial value on the plot. 386 // If the plot is null, return without doing anything. 387 private void _showInitialTrace() throws IllegalActionException { 388 if (plot == null) { 389 return; 390 } 391 392 try { 393 // Prevent update of initialTrace parameter. 394 _settingInitialTrace = true; 395 _initialTraceIsSet = true; 396 397 int lengthValue = ((IntToken) length.getToken()).intValue(); 398 ((Plot) plot).clear(0); 399 400 boolean connected = false; 401 ArrayToken defaultValues = (ArrayToken) initialTrace.getToken(); 402 403 for (int i = 0; i < lengthValue; i++) { 404 double value = 0.0; 405 406 if (defaultValues != null && i < defaultValues.length()) { 407 value = ((DoubleToken) defaultValues.getElement(i)) 408 .doubleValue(); 409 } 410 411 ((Plot) plot).addPoint(0, i, value, connected); 412 connected = true; 413 } 414 415 _setRanges(); 416 plot.repaint(); 417 } finally { 418 _settingInitialTrace = false; 419 } 420 } 421 422 // Update the initial trace parameter if the sketch on screen has 423 // been modified by the user. 424 private synchronized void _updateInitialTrace() 425 throws IllegalActionException { 426 if (_dataModified) { 427 try { 428 // Data has been modified on screen by the user. 429 Token[] record = new Token[_data[1].length]; 430 431 for (int i = 0; i < _data[1].length; i++) { 432 record[i] = new DoubleToken(_data[1][i]); 433 } 434 435 ArrayToken newValue = new ArrayToken(BaseType.DOUBLE, record); 436 initialTrace.setToken(newValue); 437 } finally { 438 _dataModified = false; 439 } 440 } 441 } 442 443 /////////////////////////////////////////////////////////////////// 444 //// private members //// 445 446 /** Current position in the signal. */ 447 private int _count; 448 449 /** Sketched data. */ 450 private double[][] _data; 451 452 /** Indicator that the user has modified the data. */ 453 private boolean _dataModified = false; 454 455 /** Indicator that initial trace has been supplied. */ 456 private boolean _initialTraceIsSet = false; 457 458 // Constant one. 459 private static IntToken _one = new IntToken(1); 460 461 // Previous value of length parameter. 462 private int _previousLengthValue = -1; 463 464 // Indicator that we are setting the initial trace. 465 private boolean _settingInitialTrace = false; 466 467 /** Zero token. */ 468 private static DoubleToken _zero = new DoubleToken(0.0); 469}