001/* An actor which pops up a keystroke-sensing JFrame.
002
003 Copyright (c) 1998-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.actor.lib.gui;
029
030import java.awt.BorderLayout;
031import java.awt.Component;
032import java.awt.event.ActionEvent;
033import java.awt.event.ActionListener;
034import java.awt.event.KeyEvent;
035import java.awt.event.MouseEvent;
036import java.awt.event.MouseListener;
037
038import javax.swing.JComponent;
039import javax.swing.JFrame;
040import javax.swing.JLabel;
041import javax.swing.KeyStroke;
042
043import ptolemy.actor.Director;
044import ptolemy.actor.TypedAtomicActor;
045import ptolemy.actor.TypedIOPort;
046import ptolemy.actor.gui.Tableau;
047import ptolemy.data.IntToken;
048import ptolemy.data.type.BaseType;
049import ptolemy.kernel.CompositeEntity;
050import ptolemy.kernel.util.IllegalActionException;
051import ptolemy.kernel.util.NameDuplicationException;
052
053///////////////////////////////////////////////////////////////////
054//// ArrowKeySensor
055
056/**
057 Detect when the user presses or releases an arrow key and produce an
058 integer on the corresponding output.
059
060 <p>When this actor is preinitialized, it pops up a new JFrame window on
061 the desktop, usually in the upper left hand corner of the screen.
062 When this JFrame has the focus (such as when it has been clicked on)
063 it is capable of sensing keystrokes.
064
065 <p>This actor senses only the four non-numeric-pad arrow-key
066 keystrokes.  This actor responds to key releases as well as key
067 presses.  Upon each key press, the integer 1 is broadcast from the
068 corresponding output.  Upon each key release, the integer 0 is output.
069
070 <p>This actor contains a private inner class which generated the JFrame.
071 The frame sets up call-backs which react to the keystrokes.  When called,
072 these call the director's fireAt() method with the current
073 time as argument.  This causes the director to call fire() on the actor with
074 the first valid time equal to the current time or later.   The actor then broadcasts
075 tokens from one or both outputs depending on which keystroke(s) have
076 occurred since the actor was last fired. On key pressed a one will be send, on key release
077 a zero will be send.
078
079 @author Winthrop Williams, Contributor: Bert Rodiers
080 @version $Id$
081 @since Ptolemy II 2.0
082 @Pt.ProposedRating Red (winthrop)
083 @Pt.AcceptedRating Red (winthrop)
084 */
085public class ArrowKeySensor extends TypedAtomicActor {
086    /** Construct an actor with the given container and name.
087     *  @param container The container.
088     *  @param name The name of this actor.
089     *  @exception IllegalActionException If the actor cannot be contained
090     *   by the proposed container.
091     *  @exception NameDuplicationException If the container already has an
092     *   actor with this name.
093     */
094    public ArrowKeySensor(CompositeEntity container, String name)
095            throws NameDuplicationException, IllegalActionException {
096        super(container, name);
097
098        // Outputs
099        upArrow = new TypedIOPort(this, "upArrow");
100        upArrow.setTypeEquals(BaseType.INT);
101        upArrow.setOutput(true);
102
103        leftArrow = new TypedIOPort(this, "leftArrow");
104        leftArrow.setTypeEquals(BaseType.INT);
105        leftArrow.setOutput(true);
106
107        rightArrow = new TypedIOPort(this, "rightArrow");
108        rightArrow.setTypeEquals(BaseType.INT);
109        rightArrow.setOutput(true);
110
111        downArrow = new TypedIOPort(this, "downArrow");
112        downArrow.setTypeEquals(BaseType.INT);
113        downArrow.setOutput(true);
114    }
115
116    ///////////////////////////////////////////////////////////////////
117    ////                     ports and parameters                  ////
118
119    /** Output port, which has type IntToken. */
120    public TypedIOPort upArrow;
121
122    /** Output port, which has type IntToken. */
123    public TypedIOPort leftArrow;
124
125    /** Output port, which has type IntToken. */
126    public TypedIOPort rightArrow;
127
128    /** Output port, which has type IntToken. */
129    public TypedIOPort downArrow;
130
131    ///////////////////////////////////////////////////////////////////
132    ////                         public methods                    ////
133
134    /** Broadcast the integer value 1 for each key pressed and 0 for
135     *  each released.
136     */
137    @Override
138    public void fire() throws IllegalActionException {
139        super.fire();
140        // Broadcast key presses.
141        if (_upKeyPressed) {
142            _upKeyPressed = false;
143            upArrow.broadcast(new IntToken(1));
144        }
145
146        if (_leftKeyPressed) {
147            _leftKeyPressed = false;
148            leftArrow.broadcast(new IntToken(1));
149        }
150
151        if (_rightKeyPressed) {
152            _rightKeyPressed = false;
153            rightArrow.broadcast(new IntToken(1));
154        }
155
156        if (_downKeyPressed) {
157            _downKeyPressed = false;
158            downArrow.broadcast(new IntToken(1));
159        }
160
161        // Broadcast key releases.
162        if (_upKeyReleased) {
163            _upKeyReleased = false;
164            upArrow.broadcast(new IntToken(0));
165        }
166
167        if (_leftKeyReleased) {
168            _leftKeyReleased = false;
169            leftArrow.broadcast(new IntToken(0));
170        }
171
172        if (_rightKeyReleased) {
173            _rightKeyReleased = false;
174            rightArrow.broadcast(new IntToken(0));
175        }
176
177        if (_downKeyReleased) {
178            _downKeyReleased = false;
179            downArrow.broadcast(new IntToken(0));
180        }
181    }
182
183    /** Create the JFrame window capable of detecting the key-presses.
184     *  @exception IllegalActionException If a derived class throws it.
185     */
186    @Override
187    public void initialize() throws IllegalActionException {
188        super.initialize();
189        _myFrame = new MyFrame();
190    }
191
192    /** Dispose of the JFrame, causing the window to vanish.
193     *  @exception IllegalActionException Not thrown in this base class.
194     */
195    @Override
196    public void wrapup() throws IllegalActionException {
197        super.wrapup();
198        if (_myFrame != null) {
199            _myFrame.dispose();
200        }
201    }
202
203    ///////////////////////////////////////////////////////////////////
204    ////                         protected variables               ////
205
206    /** A flag indicating if the down arrow key has been pressed
207     *  since the last firing of the actor.  <i>Pressed</i> and
208     *  <i>Released</i> are are not allowed to both be true for the
209     *  same key (Though both may be false).  The most recent action
210     *  (press or release) takes precedence.
211     */
212    protected boolean _downKeyPressed = false;
213
214    /** A flag indicating if the down arrow key has been released
215     *  since the last firing of the actor.
216     */
217    protected boolean _downKeyReleased = false;
218
219    /** A flag indicating if the left arrow key has been released
220     *  since the last firing of the actor.
221     */
222    protected boolean _leftKeyPressed = false;
223
224    /** A flag indicating if the left arrow key has been released
225     *  since the last firing of the actor.
226     */
227    protected boolean _leftKeyReleased = false;
228
229    /** A flag indicating if the right arrow key has been pressed
230     *  since the last firing of the actor.
231     */
232    protected boolean _rightKeyPressed = false;
233
234    /** A flag indicating if the right arrow key has been released
235     *  since the last firing of the actor.
236     */
237    protected boolean _rightKeyReleased = false;
238
239    /** A flag indicating if the up arrow key has been pressed
240     *  since the last firing of the actor.
241     */
242    protected boolean _upKeyPressed = false;
243
244    /** A flag indicating if the up arrow key has been released
245     *  since the last firing of the actor.
246     */
247    protected boolean _upKeyReleased = false;
248
249    ///////////////////////////////////////////////////////////////////
250    ////                         private variables                 ////
251
252    /** The JFrame that contains the arrow keys. */
253    private MyFrame _myFrame;
254
255    ///////////////////////////////////////////////////////////////////
256    ////                     private inner classes                 ////
257    @SuppressWarnings("serial")
258    private class MyFrame extends JFrame {
259        /** Construct a frame.  After constructing this, it is
260         *  necessary to call setVisible(true) to make the frame
261         *  appear.  This is done by calling show() at the end of this
262         *  constructor.
263         *  @see Tableau#show()
264         */
265        public MyFrame() {
266            // up-arrow call-backs
267            ActionListener myUpPressedListener = new ActionListenerExceptionCatcher(
268                    new ActionListener() {
269                        @Override
270                        public void actionPerformed(ActionEvent e) {
271                            _upKeyPressed = true;
272                            _upKeyReleased = false;
273                            _tryCallingFireAtFirstValidTime();
274                        }
275                    });
276
277            ActionListener myUpReleasedListener = new ActionListenerExceptionCatcher(
278                    new ActionListener() {
279                        @Override
280                        public void actionPerformed(ActionEvent e) {
281                            _upKeyReleased = true;
282                            _upKeyPressed = false;
283                            _tryCallingFireAtFirstValidTime();
284                        }
285                    });
286
287            // left-arrow call-backs
288            ActionListener myLeftPressedListener = new ActionListenerExceptionCatcher(
289                    new ActionListener() {
290                        @Override
291                        public void actionPerformed(ActionEvent e) {
292                            _leftKeyPressed = true;
293                            _leftKeyReleased = false;
294                            _tryCallingFireAtFirstValidTime();
295                        }
296                    });
297
298            ActionListener myLeftReleasedListener = new ActionListenerExceptionCatcher(
299                    new ActionListener() {
300                        @Override
301                        public void actionPerformed(ActionEvent e) {
302                            _leftKeyReleased = true;
303                            _leftKeyPressed = false;
304                            _tryCallingFireAtFirstValidTime();
305                        }
306                    });
307
308            // right-arrow call-backs
309            ActionListener myRightPressedListener = new ActionListenerExceptionCatcher(
310                    new ActionListener() {
311                        @Override
312                        public void actionPerformed(ActionEvent e) {
313                            _rightKeyPressed = true;
314                            _rightKeyReleased = false;
315                            _tryCallingFireAtFirstValidTime();
316                        }
317                    });
318
319            ActionListener myRightReleasedListener = new ActionListenerExceptionCatcher(
320                    new ActionListener() {
321                        @Override
322                        public void actionPerformed(ActionEvent e) {
323                            _rightKeyReleased = true;
324                            _rightKeyPressed = false;
325                            _tryCallingFireAtFirstValidTime();
326                        }
327                    });
328
329            // down-arrow call-backs
330            ActionListener myDownPressedListener = new ActionListenerExceptionCatcher(
331                    new ActionListener() {
332                        @Override
333                        public void actionPerformed(ActionEvent e) {
334                            _downKeyPressed = true;
335                            _downKeyReleased = false;
336                            _tryCallingFireAtFirstValidTime();
337                        }
338                    });
339
340            ActionListener myDownReleasedListener = new ActionListenerExceptionCatcher(
341                    new ActionListener() {
342                        @Override
343                        public void actionPerformed(ActionEvent e) {
344                            _downKeyReleased = true;
345                            _downKeyPressed = false;
346                            _tryCallingFireAtFirstValidTime();
347                        }
348                    });
349
350            getContentPane().setLayout(new BorderLayout());
351
352            JLabel label = new JLabel("This window reads Arrow Key strokes. "
353                    + "It must have the focus (be on top) to do this");
354            getContentPane().add(label);
355
356            // As of jdk1.4, the .registerKeyboardAction() method below is
357            // considered obsolete.  Docs recommend using these two methods:
358            //  .getInputMap().put(aKeyStroke, aCommand);
359            //  .getActionMap().put(aCommand, anAction);
360            // with the String aCommand inserted to link them together.
361            // See javax.swing.Jcomponent.registerKeyboardAction().
362            // Registration of up-arrow call-backs.
363            label.registerKeyboardAction(myUpPressedListener, "UpPressed",
364                    KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false),
365                    JComponent.WHEN_IN_FOCUSED_WINDOW);
366
367            label.registerKeyboardAction(myUpReleasedListener, "UpReleased",
368                    KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true),
369                    JComponent.WHEN_IN_FOCUSED_WINDOW);
370
371            // Registration of left-arrow call-backs.
372            label.registerKeyboardAction(myLeftPressedListener, "LeftPressed",
373                    KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false),
374                    JComponent.WHEN_IN_FOCUSED_WINDOW);
375
376            label.registerKeyboardAction(myLeftReleasedListener, "LeftReleased",
377                    KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true),
378                    JComponent.WHEN_IN_FOCUSED_WINDOW);
379
380            // Registration of right-arrow call-backs.
381            label.registerKeyboardAction(myRightPressedListener, "RightPressed",
382                    KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false),
383                    JComponent.WHEN_IN_FOCUSED_WINDOW);
384
385            label.registerKeyboardAction(myRightReleasedListener,
386                    "RightReleased",
387                    KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true),
388                    JComponent.WHEN_IN_FOCUSED_WINDOW);
389
390            // Registration of down-arrow call-backs.
391            label.registerKeyboardAction(myDownPressedListener, "DownPressed",
392                    KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false),
393                    JComponent.WHEN_IN_FOCUSED_WINDOW);
394
395            label.registerKeyboardAction(myDownReleasedListener, "DownReleased",
396                    KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true),
397                    JComponent.WHEN_IN_FOCUSED_WINDOW);
398
399            label.setRequestFocusEnabled(true);
400            label.addMouseListener(new FocusMouseListener());
401
402            // Set the default size.
403            // Note that the location is of the frame, while the size
404            // is of the scrollpane.
405            pack();
406            setVisible(true);
407        }
408
409        /* A mouse listener that requests focus for the source of any
410         * mouse event it receives.
411         */
412        private/*static*/class FocusMouseListener implements MouseListener {
413            // FindBugs suggests making this class static so as to decrease
414            // the size of instances and avoid dangling references.
415            // However, Java does allow inner classes of inner classes to be
416            // static.
417
418            // This is a copy of diva/gui/toolbox/FocusMouseListener.java
419            // because we don't want the dependency on diva.
420
421            /** Invoked when the mouse is released.
422             *  Ignored in this listener.
423             *  @param event The corresponding event.
424             */
425            @Override
426            public void mouseReleased(MouseEvent event) {
427            }
428
429            /** Invoked when the mouse enters a component.
430             *  Ignored in this listener.
431             *  @param event The corresponding event.
432             */
433            @Override
434            public void mouseEntered(MouseEvent event) {
435            }
436
437            /** Invoked when the mouse exits a component.
438             *  Ignored in this listener.
439             *  @param event The corresponding event.
440             */
441            @Override
442            public void mouseExited(MouseEvent event) {
443            }
444
445            /**
446             * Grab the keyboard focus when the component that this
447             * listener is attached to is clicked on.
448             *  @param event The corresponding event.
449             */
450            @Override
451            public void mousePressed(MouseEvent event) {
452                Component component = event.getComponent();
453
454                if (!component.hasFocus()) {
455                    component.requestFocus();
456                }
457            }
458
459            /** Invoked when the mouse is clicked (pressed and released).
460             *  Ignored in this listener.
461             *  @param event The corresponding event.
462             */
463            @Override
464            public void mouseClicked(MouseEvent event) {
465            }
466        }
467
468        ///////////////////////////////////////////////////////////////////
469        ////                     private methods                        ////
470
471        /** We schedule a fire as soon as possible.
472         */
473        private void _tryCallingFireAtFirstValidTime() {
474            Director director = getDirector();
475            try {
476                director.fireAt(ArrowKeySensor.this, director.getModelTime());
477            } catch (IllegalActionException ex) {
478                throw new RuntimeException(ex);
479            }
480        }
481    }
482
483    private static class ActionListenerExceptionCatcher
484            implements ActionListener {
485        ///////////////////////////////////////////////////////////////////
486        ////                         public methods                    ////
487
488        /** Construct an instance that will wrap an ActionListener,
489         * catch its exceptions and report it to the Ptolemy
490         * Message Handler.
491         * @param actionListener The actionListener.
492         */
493        public ActionListenerExceptionCatcher(ActionListener actionListener) {
494            _actionListener = actionListener;
495        }
496
497        @Override
498        public void actionPerformed(ActionEvent e) {
499            try {
500                _actionListener.actionPerformed(e);
501            } catch (Throwable ex) {
502                ptolemy.util.MessageHandler.error(
503                        ptolemy.util.MessageHandler.shortDescription(ex), ex);
504            }
505        }
506
507        ///////////////////////////////////////////////////////////////////
508        ////                         private variables                 ////
509
510        // The runnable.
511        private ActionListener _actionListener;
512    }
513
514}