001/* A live signal plotter.
002
003 @Copyright (c) 1997-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.plot;
029
030import java.awt.event.ActionEvent;
031import java.awt.event.ActionListener;
032
033import javax.swing.JButton;
034
035///////////////////////////////////////////////////////////////////
036//// PlotLive
037
038/**
039 Plot signals dynamically, where points can be added at any time
040 and the display will be updated.  This should be normally used
041 with some finite persistence so that old points are erased as new
042 points are added.  Unfortunately, the most efficient way to erase
043 old points is to draw graphics using the "exclusive or" mode, which
044 introduces quite a number of artifacts.  When lines are drawn
045 between points, where they overlap the points the line becomes
046 white. Moreover, if two lines or points overlap completely, they
047 disappear.
048 <p>
049 This class is abstract, so it must be used by creating a derived
050 class.  To use it, create a derived class with an
051 addPoints() method. Your class may also set graph parameters like
052 titles and axis labels in the constructor by calling
053 methods in the Plot or PlotBox classes (both of which are base classes).
054 The addPoints() method should call addPoint() of the Plot base
055 class to dynamically add points to the plot.  This method is called
056 within a thread separate from the applet thread, so the zooming
057 mechanism and buttons remain live.
058
059 @author Edward A. Lee, Christopher Brooks, Contributor: Jeff Lane
060 @version $Id$
061 @since Ptolemy II 0.2
062 @Pt.ProposedRating Yellow (cxh)
063 @Pt.AcceptedRating Yellow (cxh)
064 */
065@SuppressWarnings("serial")
066public abstract class PlotLive extends Plot implements Runnable {
067    ///////////////////////////////////////////////////////////////////
068    ////                         public methods                    ////
069
070    /** Redefine in derived classes to add points to the plot.
071     *  Adding many points at once will make the plot somewhat faster
072     *  because the thread yields between calls to this method.
073     *  However, the plot will also be somewhat less responsive to user
074     *  inputs such as zooming, filling, or stopping.  In the derived-class
075     *  implementation, this method should probably be synchronized.
076     *
077     *  <p>Jeff Lane points out that if the derived class version of
078     *  addPoints() does not return quickly, and it seems like then you
079     *  may want to experiment with addPoints() to not being synchronized.
080     */
081    public abstract void addPoints();
082
083    /** Make start and stop buttons.
084     *  This method is deprecated.  Use setButtons() instead.
085     *  @deprecated
086     */
087    @Deprecated
088    public void makeButtons() {
089        if (_startButton == null) {
090            _startButton = new JButton("start");
091            _startButton.addActionListener(new StartButtonListener());
092            add(_startButton);
093        }
094
095        _startButton.setVisible(true);
096
097        if (_stopButton == null) {
098            _stopButton = new JButton("stop");
099            _stopButton.addActionListener(new StopButtonListener());
100            add(_stopButton);
101        }
102
103        _stopButton.setVisible(true);
104        _stopButton.setEnabled(false);
105        _startButton.setEnabled(true);
106    }
107
108    /** Pause the plot.  To resume, call start().
109     */
110    public void pause() {
111        _paused = true;
112        _plotting = false;
113        _stopButton.setEnabled(false);
114        _startButton.setEnabled(true);
115    }
116
117    /** This is the body of a thread that repeatedly calls addPoints()
118     *  if the plot is active.  To make the plot active, call start().
119     *  To pause the plot, call pause().  To stop the plot and destroy
120     *  the thread, call stop().  The next time start() is called, a new
121     *  thread will be started. Between calls to addPoints(), this method calls
122     *  Thread.yield() so that the thread does not hog all
123     *  the resources.  This somewhat slows down execution, so derived
124     *  classes may wish to plot quite a few points in their
125     *  addPoints() method, if possible.  However,
126     *  plotting more points at once may also decrease the
127     *  responsiveness of the user interface.
128     */
129    @Override
130    public void run() {
131        while (_plotting || _paused) {
132            if (_plotting) {
133                addPoints();
134
135                // Give the event thread a chance.
136                Thread.yield();
137            } else if (_paused) {
138                // NOTE: Cannot synchronize this entire method because then
139                // the Thread.yield() call above does not yield to any
140                // synchronized methods (like _drawPlot()).
141                synchronized (this) {
142                    try {
143                        wait();
144                    } catch (InterruptedException e) {
145                    }
146                }
147            }
148        }
149    }
150
151    /** If the argument is true, make a start, stop, and fill button
152     *  visible at the upper right.  Otherwise, make the buttons invisible.
153     *  NOTE: The buttons may infringe on the title space,
154     *  if the title is long.  In an application, it is preferable to provide
155     *  a menu with the commands.  This way, when printing the plot,
156     *  the printed plot will not have spurious buttons.  Thus, this method
157     *  should be used only by applets, which normally do not have menus.
158     */
159    @Override
160    public void setButtons(boolean visible) {
161        super.setButtons(visible);
162
163        if (_startButton == null) {
164            _startButton = new JButton("start");
165            _startButton.addActionListener(new StartButtonListener());
166            add(_startButton);
167        }
168
169        _startButton.setVisible(visible);
170
171        if (_stopButton == null) {
172            _stopButton = new JButton("stop");
173            _stopButton.addActionListener(new StopButtonListener());
174            add(_stopButton);
175        }
176
177        _stopButton.setVisible(visible);
178
179        if (visible) {
180            _stopButton.setEnabled(false);
181            _startButton.setEnabled(true);
182        }
183    }
184
185    /** Make the plot active.  Start a new thread if necessary.
186     */
187    public synchronized void start() {
188        _plotting = true;
189        _paused = false;
190        if (_stopButton != null) {
191            _stopButton.setEnabled(true);
192        }
193        if (_startButton != null) {
194            _startButton.setEnabled(false);
195        }
196        if (_plotLiveThread == null) {
197            _plotLiveThread = new Thread(this, "PlotLive Thread");
198            _plotLiveThread.start();
199        } else {
200            // synchronized (this) { is useless since the method is already
201            // synchronized.
202            // synchronized (this) {
203            notifyAll();
204            //}
205        }
206    }
207
208    /** Stop the plot.  The plot thread exits.  This should be called by
209     *  an applet's stop() method.
210     */
211    public void stop() {
212        _plotting = false;
213        _paused = false;
214
215        // FindBugs: [M M IS] Inconsistent synchronization [IS2_INCONSISTENT_SYNC]
216        // Actually this is not an issue, since once the thread has been created this
217        // member is not accessed until its destruction.
218        _plotLiveThread = null;
219    }
220
221    ///////////////////////////////////////////////////////////////////
222    ////                         private variables                 ////
223
224    /** @serial Thread of this plotter */
225    private Thread _plotLiveThread = null;
226
227    /** @serial True if we are actually plotting. */
228    private boolean _plotting = false;
229
230    /** @serial True if we are paused. */
231    private boolean _paused = false;
232
233    /** @serial Start and Stop Buttons. */
234    private JButton _startButton;
235
236    /** @serial Start and Stop Buttons. */
237    private JButton _stopButton;
238
239    ///////////////////////////////////////////////////////////////////
240    ////                         inner classes                     ////
241    class StartButtonListener implements ActionListener {
242        @Override
243        public void actionPerformed(ActionEvent event) {
244            start();
245        }
246    }
247
248    // Despite the name, the stop button calls pause.
249    class StopButtonListener implements ActionListener {
250        @Override
251        public void actionPerformed(ActionEvent event) {
252            pause();
253        }
254    }
255}