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}