001/* An actor that generates an empty token in response to a click of a button.
002
003 @Copyright (c) 1998-2018 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 java.awt.Frame;
031
032import ptolemy.actor.Director;
033import ptolemy.actor.TypedAtomicActor;
034import ptolemy.actor.TypedIOPort;
035import ptolemy.actor.gui.EditorFactory;
036import ptolemy.actor.util.Time;
037import ptolemy.data.DoubleToken;
038import ptolemy.data.expr.Parameter;
039import ptolemy.data.type.BaseType;
040import ptolemy.kernel.CompositeEntity;
041import ptolemy.kernel.util.IllegalActionException;
042import ptolemy.kernel.util.NameDuplicationException;
043import ptolemy.kernel.util.NamedObj;
044import ptolemy.kernel.util.Settable;
045import ptolemy.kernel.util.Workspace;
046import ptolemy.moml.MoMLChangeRequest;
047import ptolemy.util.CancelException;
048import ptolemy.util.MessageHandler;
049
050///////////////////////////////////////////////////////////////////
051//// EventButton
052
053/**
054 Output a token when the actor is fired.
055 This actor customizes its interaction to request a firing
056 whenever the icon is double clicked.
057
058 By default, the value of the output is a boolean true.
059 To change this, Alt-click on the icon.
060
061 @author  Edward A. Lee
062 @version $Id$
063 @since Ptolemy II 11.0
064 @Pt.ProposedRating Red (winthrop)
065 @Pt.AcceptedRating Red (winthrop)
066 */
067public class EventButton extends TypedAtomicActor {
068
069    /** Construct an actor.
070     *  @param container The container.
071     *  @param name The name of this actor.
072     *  @exception IllegalActionException If the entity cannot be contained
073     *   by the proposed container.
074     *  @exception NameDuplicationException If the container already has an
075     *   actor with this name.
076     */
077    public EventButton(CompositeEntity container, String name)
078            throws IllegalActionException, NameDuplicationException {
079        super(container, name);
080
081        output = new TypedIOPort(this, "output", false, true);
082
083        value = new Parameter(this, "value");
084        value.setExpression("true");
085
086        output.setTypeAtLeast(value);
087
088        new DoubleClickHandler(this, "_doubleClickHandler");
089
090        buttonPressed = new Parameter(this, "buttonPressed");
091        buttonPressed.setTypeEquals(BaseType.BOOLEAN);
092        buttonPressed.setExpression("false");
093        buttonPressed.setVisibility(Settable.NONE);
094
095        pressDuration = new Parameter(this, "pressDuration");
096        pressDuration.setExpression("0.2");
097        pressDuration.setTypeEquals(BaseType.DOUBLE);
098    }
099
100    ///////////////////////////////////////////////////////////////////
101    ////        public variables and parameters                    ////
102
103    /** Hidden parameter controlling the visual rendition of the button.
104     */
105    public Parameter buttonPressed;
106
107    /** The output port.  The type of this port is the same as that of
108     *  the value parameter.
109     */
110    public TypedIOPort output;
111
112    /** Amount of time to keep the button depressed, in seconds.
113     *  Additional button presses during this time will be ignored.
114     *  This is a double with default value 0.2.
115     */
116    public Parameter pressDuration;
117
118    /** The value produced. This is a boolean true by default.
119     */
120    public Parameter value;
121
122    ///////////////////////////////////////////////////////////////////
123    ////                         public methods                    ////
124
125    /** Clone the actor into the specified workspace.
126     *  This method sets the type constraint of the output
127     *  to be at least the type of the value.
128     *  @param workspace The workspace for the new object.
129     *  @return A new actor.
130     *  @exception CloneNotSupportedException If a derived class contains
131     *   an attribute that cannot be cloned.
132     */
133    @Override
134    public Object clone(Workspace workspace) throws CloneNotSupportedException {
135        EventButton newObject = (EventButton) super.clone(workspace);
136
137        // set the type constraints.
138        newObject.output.setTypeAtLeast(newObject.value);
139        return newObject;
140    }
141
142    /** Fire the actor.
143     */
144    @Override
145    public synchronized void fire() throws IllegalActionException {
146        super.fire();
147
148        // Do not produce an output if the purpose of the firing is
149        // to restore the button.
150        // Here, we assume that firings occur _only_ in response
151        // to double clicks.
152        if (_bounceBackTime != null) {
153            // Restore the button.
154            _setButtonPressed(false);
155            _bounceBackTime = null;
156        } else {
157            output.broadcast(value.getToken());
158
159            Director director = getDirector();
160            Time currentTime = director.getModelTime();
161
162            double pressDurationValue = ((DoubleToken) pressDuration.getToken())
163                    .doubleValue();
164            // Mark that the button is depressed.
165            _bounceBackTime = currentTime.add(pressDurationValue);
166            // Request a firing in the future to restore the button.
167            director.fireAt(EventButton.this, _bounceBackTime);
168
169            // Mark the button pressed.
170            _setButtonPressed(true);
171        }
172    }
173
174    /** Mark that the model is now executing.
175     *  @exception IllegalActionException If the superclass throws it.
176     */
177    @Override
178    public void initialize() throws IllegalActionException {
179        super.initialize();
180        _running = true;
181        _bounceBackTime = null;
182    }
183
184    /** Mark that the model is no longer executing.
185     *  @exception IllegalActionException If the superclass throws it.
186     */
187    @Override
188    public void wrapup() throws IllegalActionException {
189        super.wrapup();
190        _running = false;
191        _setButtonPressed(false);
192        _bounceBackTime = null;
193    }
194
195    ///////////////////////////////////////////////////////////////////
196    ////                         private methods                   ////
197
198    /** Set whether the button is pressed.
199     *  @param pressed True to be pressed.
200     */
201    private void _setButtonPressed(boolean pressed) {
202        String moml = "<property name=\"buttonPressed\" value=\"" + pressed
203                + "\"/>";
204        MoMLChangeRequest request = new MoMLChangeRequest(this, this, moml);
205        request.setPersistent(false);
206        requestChange(request);
207    }
208
209    ///////////////////////////////////////////////////////////////////
210    ////                         private members                   ////
211
212    /** Time at which the button should revert to full size. */
213    private Time _bounceBackTime;
214
215    /** Indicator that the model is running. */
216    private boolean _running;
217
218    ///////////////////////////////////////////////////////////////////
219    ////                         inner classes                     ////
220
221    /** Class to respond to double click. */
222    class DoubleClickHandler extends EditorFactory {
223
224        public DoubleClickHandler(NamedObj container, String name)
225                throws IllegalActionException, NameDuplicationException {
226            super(container, name);
227        }
228
229        /** Respond to double click. */
230        @Override
231        public void createEditor(NamedObj object, Frame parent) {
232            if (!_running) {
233                try {
234                    MessageHandler.warning(
235                            "Model is not running. No output produced.");
236                } catch (CancelException e) {
237                    // Ignore.
238                }
239                return;
240            }
241            Director director = getDirector();
242            if (director != null) {
243                try {
244                    // This will be called in the Swing event thread.
245                    // Make sure that the actor is not currently firing.
246                    synchronized (EventButton.this) {
247                        if (_bounceBackTime != null) {
248                            // Ignore.  Still in the interval of the previous button press.
249                            return;
250                        }
251                        // Request a firing now to produce an output.
252                        director.fireAtCurrentTime(EventButton.this);
253                    }
254                } catch (IllegalActionException e) {
255                    MessageHandler.error(
256                            "Director is unable to fire the actor as requested.",
257                            e);
258                }
259            } else {
260                MessageHandler.error("No director!");
261            }
262        }
263    }
264}